What is the purpose of `serialVersionUID`?
The serialVersionUID
is a unique ID used during the serialization and deserialization process. It is a private static final long
field in a class that implements `Serializable`.
Its purpose is to provide a version number for the class to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization.
If the UIDs match, the object is deserialized. If they do not match, it indicates that the local class version is incompatible with the serialized object's version, and an `InvalidClassException` is thrown. This prevents unexpected errors that could arise from deserializing an object into an incompatible class structure.
If you don't declare a `serialVersionUID`, the JVM will generate one automatically based on the class details (fields, methods, etc.). However, it is strongly recommended to explicitly declare one, because even minor compiler differences or class modifications can cause the auto-generated UID to change, breaking deserialization.
Explain the full lifecycle of a thread.
A thread in Java goes through several states during its lifetime. These states are defined in the `Thread.State` enum.
What is a `ThreadPoolExecutor` and what are its core parameters?
A `ThreadPoolExecutor` is a powerful and flexible class from the `java.util.concurrent` package that creates and manages a pool of worker threads. It separates the task submission from the task execution, allowing for better resource management by reusing existing threads instead of creating a new one for every task.
The main constructor has several key parameters:
corePoolSize
: The number of threads to keep in the pool, even if they are idle.maximumPoolSize
: The maximum number of threads allowed in the pool.keepAliveTime
: When the number of threads is greater than the core size, this is the maximum time that excess idle threads will wait for new tasks before terminating.unit
: The time unit for the `keepAliveTime` argument (e.g., `TimeUnit.SECONDS`).workQueue
: The queue used to hold tasks before they are executed. If all core threads are busy, new tasks are placed in this queue. Examples include `LinkedBlockingQueue`, `ArrayBlockingQueue`, and `SynchronousQueue`.threadFactory
: A factory used to create new threads when needed.handler
: The policy to use when a task is rejected because the thread pool is saturated (both the maximum pool size and the queue are full). Examples include `AbortPolicy` (throws an exception) and `CallerRunsPolicy` (the submitting thread runs the task itself).What is `Callable` and how is it different from `Runnable`?
Both `Callable` and `Runnable` are interfaces representing tasks that can be executed by a thread. However, `Callable` is a more powerful version introduced in Java 5.
Feature | Runnable | Callable<V> |
---|---|---|
Method Signature | It has a single abstract method: void run() . | It has a single abstract method: V call() . |
Return Value | The run() method does not return any value (its return type is `void`). | The call() method can return a result of type `V`. |
Exception Handling | The run() method cannot throw a checked exception. You must handle it inside the method. | The call() method can throw a checked exception. |
Submission | Submitted to an `ExecutorService` using the execute(Runnable) method. | Submitted to an `ExecutorService` using the submit(Callable) method. This method returns a `Future` object. |
A Future
object represents the result of an asynchronous computation. You can use it to check if the computation is complete, wait for its completion, and retrieve the result from the `call()` method.
What is a `CompletableFuture`?
CompletableFuture
, introduced in Java 8, is an evolution of the `Future` interface. It represents a future result of an asynchronous computation but provides much more powerful capabilities for composing, combining, and handling these computations.
Key advantages over a simple `Future`:
thenApply()
, thenAccept()
, and thenRun()
.thenCompose()
are used for this.thenCombine()
, allOf()
, and _anyOf()
.exceptionally()
and handle()
.It is a cornerstone of modern asynchronous programming in Java.
Explain the Java Memory Model (JMM).
The Java Memory Model (JMM) defines the rules for how threads in a multi-threaded Java application interact through memory. It specifies the conditions under which a write to a variable by one thread is guaranteed to be visible to a read of that same variable by another thread.
Key concepts of the JMM include:
volatile
and `synchronized` are used to enforce these visibility guarantees.Without the JMM, it would be impossible to write correct and predictable concurrent programs in Java.
What is the difference between Heap and Stack memory?
Heap and Stack are two different memory areas used by the JVM.
Stack Memory:
StackOverflowError
.Heap Memory:
OutOfMemoryError
.What is Garbage Collection and how does it work?
Garbage Collection (GC) is the process by which the Java Virtual Machine (JVM) automatically manages memory. Its purpose is to identify and discard objects that are no longer in use by the application, thereby freeing up heap memory.
The basic process is often called Mark and Sweep:
Modern JVMs use a Generational Garbage Collection strategy. The heap is divided into generations:
This strategy is efficient because it makes the assumption that most objects are short-lived and can be collected quickly.
Can you force garbage collection in Java?
No, you cannot force garbage collection in Java. You can only request or suggest it.
The `System.gc()` method can be called to suggest that the JVM perform garbage collection. However, the Java Language Specification states:
"Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects... When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects."
This means the JVM is not obligated to run the garbage collector immediately or at all. The decision is up to the JVM's internal algorithms.
In practice, calling `System.gc()` is highly discouraged. It can interfere with the JVM's own sophisticated GC scheduling and potentially cause performance issues rather than solve them. It's almost always better to let the JVM manage memory on its own.
What are Generics in Java?
Generics, introduced in Java 5, are a feature that provides type safety for collections and methods by allowing you to create classes, interfaces, and methods that operate on types as parameters.
Before generics, you would store objects in a collection like `ArrayList` and would have to cast them back to their specific type when retrieving them. This was error-prone, as you could accidentally add an object of the wrong type, leading to a `ClassCastException` at runtime.
Benefits of Generics:
// Without Generics (unsafe and requires casting)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
// With Generics (type-safe and no casting needed)
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);