3 Understanding Threads and Locks

This chapter contains basic information about threads and locks in the JRockit JVM.

A running application usually consists of one process with its own memory space. A computer generally runs several processes at the same time. For example, a word processor application process might run at the same time as a media player application process. A process consists of many threads that run concurrently. When you run a Java application, a new JVM process starts.

Each Java process has at least one application thread. In addition to the application threads, the Oracle JRockit JVM uses internal threads for garbage collection, code generation, and other internal purposes.

You can use thread dumps (printouts of the stacks of all the threads in an application) to diagnose problems and optimize application and JVM performance. For information about generating thread dumps, see "Using Thread Dumps" in Oracle JRockit JDK Tools.

The following sections describe the threads and locks in the JRockit JVM:

3.1 Understanding Threads

A Java application consists of one or more threads that run Java code. The JVM process consists of the Java threads and some JVM-internal threads, for example one or more garbage collection threads, a code optimizer thread, and one or more finalizer threads.

The operating system handles Java threads as it does any application thread. Thread scheduling and priorities are handled by the operating system.

Within Java, the Java threads are represented by thread objects. Each thread also has a stack, used for storing run-time data. The thread stack has a specific size. If a thread tries to store more items on the stack than the stack size allows, the thread throws a stack overflow error.

Default Stack Sizes for Java Application Threads

The default stack sizes vary depending on the operating system you use. Table 3-1 lists the default stack sizes for different operating systems.

Table 3-1 Default Stack Size

Operating System Default Stack Size

Windows IA32

64 KB

Windows x64

128 KB

Linux IA32

128 KB

Linux x64

256 KB

Solaris/SPARC

512 KB


You can change the thread stack size with the -Xss command-line option (for example, java -Xss:512k MyApplication

Default Stack Size for JVM Internal Threads

A special "system" stack size (256 KB on all platforms) is used for JVM internal threads such as the garbage collection and code generation threads.

Note:

The -Xss command-line option sets the stack size of both application threads and JVM internal threads.

3.2 Understanding Locks

When threads in a process share and update the same data, their activities must be synchronized to avoid errors. In Java, this is done with the synchronized keyword, or with the wait and notify keywords. Synchronization is achieved by the use of locks, each of which is associated with an object. For a thread to work on an object, it must control (hold) the associated lock. Only one thread can hold a lock at a time. If a thread tries to take control of a lock that is already held by another thread, then it must wait until the lock is released. When this happens, there is contention for the lock.

Locks can be of four types:

  • Thin locks

    A thread that tries to acquire a lock that is held by another thread spins for a short while. While it spins, the thread continuously checks whether the lock it needs is still held by the other thread. This is the default behavior on multiple-CPU systems. Such a lock is called a thin lock.

  • Fat locks

    If a thread trying to acquire a lock spins for too long, the CPU resource is needlessly tied down and not available to execute other code. The CPU resource can be released, by inflating the thin lock into a fat lock. The operating system then manages the thread till the required lock becomes available, freeing the CPU for other tasks.

  • Recursive locks

    A lock is recursive if the synchronized code calls itself, either directly or indirectly.

  • Lazy locks

    A lazy lock is not released when a critical section is exited. After a lazy lock is acquired by a thread, other threads that try to acquire that lock must ensure that the lock is, or can be, released.

The JRockit JVM uses a complex set of heuristics to determine when to change from one type of lock to another.

Lock Chains

Several threads can be tied up in what is called a lock chain. A lock chain can be defined as follows:

  • Threads A and B form a lock chain if thread A holds a lock that thread B is needs. If A is not trying to take a lock, then the lock chain is considered to be open.

  • If A–B is a lock chain, and B–C is a lock chain, then A–B–C is a more complete lock chain.

  • If there is no additional thread waiting for a lock held by C, then A–B–C is a complete and open lock chain.

Lock Chain Types

The JRockit JVM analyzes the threads and forms complete lock chains. There are three possible types of lock chains: open, deadlocked and blocked.

  • Open chains

    Open chains represent a straight dependency: thread A is waiting for B which is waiting for C, and so on. If you have long open chains, your application might be wasting time waiting for locks. If this is the case, then reconsider how locks are used for synchronization in your application.

  • Deadlocked chains

    A deadlocked, or circular, chain consists of a chain of threads, in which the first thread in the chain is waiting for the last thread in the chain. In the simplest case, thread A is waiting for thread B, while thread B is waiting for thread A. In thread dumps, the JRockit JVM selects an arbitrary thread to display as the first thread in the chain.

    Deadlocks cannot be resolved, and the application waits indefinitely.

  • Blocked chains

    A blocked chain is a lock chain whose head thread is also part of another lock chain, which can be either open or deadlocked. For example, if thread A is waiting for thread B, thread B is waiting for thread A, and thread C is waiting for thread A, then thread A and B form a deadlocked lock chain, while thread C and thread A form a blocked lock chain.