Type Loading, Linking, and Initialization

JVM makes types available to the running program through a process of loading, linking, and initialization (in that order). Linking is divided into three sub-steps: verification, preparation, and resolution.

All implementations must initialize each class or interface on its first active use. The following six situations qualify as active uses:

The initialization of a class requires prior initialization of its superclass. Applied recursively, this rule means that all of a class’s superclasses must be initialized prior to the initialization of the class. The same is not true, however, of interfaces. An interface is initialized only because a non-constant field declared by the interface is used.

Loading

After that a type is created. Types are loaded either through the bootstrap class loader or through user-defined class loaders. Former loads classes and interfaces of the Java API in an implementation-dependent way; latter which are instances of subclasses of java.lang.ClassLoader, load classes in custom ways.

Verification:

After a type is loaded, it is ready to be linked. The first step of the linking process is verification– ensuring that the type obeys the semantics of the Java language and that it won’t violate the integrity of the virtual machine. Here are some of the things that are good candidates for checking during the official verification phase:

Preparation:

After a Java virtual machine has loaded a class and performed whatever verification it chooses to do up front, the class is ready for preparation. During the preparation phase, the Java virtual machine allocates memory for the class variables and sets them to default initial values.

Type Initial Value
int 0
long 0L
short (short) 0
char ‘\u0000’
byte (byte) 0
boolean false
reference null
float 0.0f
double 0.0d

Resolution:

After a type has been through the first two phases of linking: verification and preparation, it is ready for the third and final phase of linking: resolution. Resolution is the process of locating classes, interfaces, fields, and methods referenced symbolically from a type’s constant pool, and replacing those symbolic references with direct references. As mentioned above, this phase of linking is optional until (and unless) each symbolic reference is first used by the program.

Initialization:

The final step required to ready a class or interface for its first active use is initialization, the process of setting class variables to their proper initial values. a proper initial value is specified via a class variable initializer or static initializer:

class A {
    //variable initializer
    static int a = 1;
    // static initializer 
    static int b;
    static {
        b = 1;
    }
}

All the class variable initializers and static initializers of a type are collected by the Java compiler and placed into one special method called clinit which will later be invoked by JVM.

Initialization of a class consists of two steps:

Initialization of an interface consists of only one step since it does not require initialization of its superinterfaces:

Note that the initialization process is properly synchronized. That’s why the inner static helper class singleton is thread safe.

Thread

A thread is a thread of execution in a program.

When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started.

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started.

notify, notifyAll and wait(all methods of Object) should only be called by a thread that is the owner of this object’s monitor.

A thread becomes the owner of the object’s monitor in one of three ways:

Only one thread at a time can own an object’s monitor.

notify wakes up a single thread that is waiting on this object’s monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. notifyAll on the other hand wakes up all threads that are waiting on this object’s monitor. The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.

A thread waits on an object’s monitor by calling one of the wait methods.

wait causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. Thread T becomes disabled for thread scheduling purposes and lies dormant until one of four things happens:

The thread T is then removed from the wait set for this object and re-enabled for thread scheduling. It then competes in the usual manner with other threads for the right to synchronize on the object; once it has gained control of the object, all its synchronization claims on the object are restored to the status quo ante - that is, to the situation as of the time that the wait method was invoked. Thread T then returns from the invocation of the wait method. Thus, on return from the wait method, the synchronization state of the object and of thread T is exactly as it was when the wait method was invoked.

A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:

synchronized (obj) {
    while (<condition does not hold>)
        obj.wait(timeout);
        ... // Perform action appropriate to condition
}

If the current thread is interrupted by any thread before or while it is waiting, then an InterruptedException is thrown. This exception is not thrown until the lock status of this object has been restored as described above.

Note that the wait method, as it places the current thread into the wait set for this object, unlocks only this object; any other objects on which the current thread may be synchronized remain locked while the thread waits.

interrupt interrupts current thread. An interrupt is an indication to a thread that it should stop what it is doing and do something else. It’s up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate.

Interrupting a thread that is not alive need not have any effect. The interrupt mechanism is implemented using an internal flag known as the interrupt status. Invoking Thread.interrupt sets this flag. When a thread checks for an interrupt by invoking the static method Thread.interrupted, interrupt status is cleared. The non-static isInterrupted method, which is used by one thread to query the interrupt status of another, does not change the interrupt status flag.

sleep causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.

join waits for the thread to die.

Methods such as sleep, join and wait that could throw InterruptedException are designed to cancel their current operation and return immediately when an interrupt is received. Also the interrupted status of the current thread is cleared when this exception is thrown.

Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object’s fields has to acquire the object’s intrinsic lock before accessing them, and then release the intrinsic lock when it’s done with them. A thread is said to own the intrinsic lock between the time it has acquired the lock and released the lock. As long as a thread owns an intrinsic lock, no other thread can acquire the same lock. The other thread will block when it attempts to acquire the lock. When a thread releases an intrinsic lock, a happens-before relationship is established between that action and any subsequent acquisition of the same lock.

Lock

Lock objects work very much like the implicit locks used by synchronized code. As with implicit locks, only one thread can own a Lock object at a time. Lock objects also support a wait/notify mechanism, through their associated Condition objects.

The use of synchronized methods or statements provides access to the implicit monitor lock associated with every object, but forces all lock acquisition and release to occur in a block-structured way: when multiple locks are acquired they must be released in the opposite order, and all locks must be released in the same lexical scope in which they were acquired. While the scoping mechanism for synchronized methods and statements makes it much easier to program with monitor locks, and helps avoid many common programming errors involving locks, there are occasions where you need to work with locks in a more flexible way. For example, some algorithms for traversing concurrently accessed data structures require the use of “hand-over-hand” or “chain locking”: you acquire the lock of node A, then node B, then release A and acquire C, then release B and acquire D and so on. Implementations of the Lock interface enable the use of such techniques by allowing a lock to be acquired and released in different scopes, and allowing multiple locks to be acquired and released in any order.

The biggest advantage of Lock objects over implicit locks is their ability to back out of an attempt to acquire a lock. The tryLock method backs out if the lock is not available immediately or before a timeout expires (if specified). The lockInterruptibly method backs out if another thread sends an interrupt before the lock is acquired.

The constructor for ReentrantLock accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation. Note however, that fairness of locks does not guarantee fairness of thread scheduling. Thus, one of many threads using a fair lock may obtain it multiple times in succession while other active threads are not progressing and not currently holding the lock. Also note that the untimed tryLock method does not honor the fairness setting. It will succeed if the lock is available even if other threads are waiting.

Interrupting a thread which is waiting “uninterruptedly” on a Lock , ReentrantLock or synchronized block will merely result in the thread waking up and seeing if it’s allowed to take the lock yet, by whatever mechanism is in place in the defining lock, and if it cannot it parks again until it is interrupted again or told it can take the lock. When the thread can proceed it simply proceeds with its interrupted flag set.

Contrast to lockInterruptibly where, actually, if you are interrupted, you do not ever get the lock, and instead you “abort” trying to get the lock and the lock request is cancelled.

ThreadPoolExecutor:

If the pool currently has more than corePoolSize threads, excess threads will be terminated if they have been idle for more than the keepAliveTime. This provides a means of reducing resource consumption when the pool is not being actively used. By default, the keep-alive policy applies only when there are more than corePoolSize threads. But method allowCoreThreadTimeOut(boolean) can be used to apply this time-out policy to core threads as well, so long as the keepAliveTime value is non-zero.

Queuing:

Any BlockingQueue may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:

There are three general strategies for queuing:

Rejected tasks:

New tasks submitted in method execute(Runnable) will be rejected when the Executor has been shut down, and also when the Executor uses finite bounds for both maximum threads and work queue capacity, and is saturated. In either case, the execute method invokes the RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor) method of its RejectedExecutionHandler. Four predefined handler policies are provided:

It is possible to define and use other kinds of RejectedExecutionHandler classes. Doing so requires some care especially when policies are designed to work only under particular capacity or queuing policies.

GC

Heap consists of three parts – young, old and meta-space, young space is further divided into eden, s0 and s1:

|      YOUNG     | OLD | META |
| EDEN | SO | S1 |
GC OPTION YOUNG OLD DESCRIPTION
-XX:+UseSerialGC single single single core cpu/ small heap
-XX:+UseParallelGC single parallel  
-XX:+UseParallelOldGC parallel parallel  
-XX:+UsePArNewGC parallel    
-XX:+UseConsMarkSweepGC parallel concurrent auto-enables ParNewGC
-XX:+UseG1GC g1 g1  

Use CMS when:

Use parallel collector when :

Throughput collectors can automatically tune themselves:

Compressed Object Pointers

  Uncompressed Compressed 32-bit
Pointer 8 4 4
Object header 16 12* 8
Array header 24 14 12
Superclass pad 8 4 4

(* Object can have 4 bytes of fields and still only take up 16 bytes)

(updating…)