JVM – 5 [ Threads Shared Runtime Data Area ]

Memory is a very important system resource.

It is the intermediate bridge between the hard disk and the CPU, and carries the real-time operation of the operating system and applications.

Below is architecture of JVM memory runtime data area :

Let us focus on Threads Shared Area.

What You Need

  • About 15 minutes
  • A favorite text editor or IDE
  • Java 8 or later

1. Heap

The stack solves the problem of running a program, that is, how the program executes, or how data is processed.

The heap solves the problem of data storage, that is, how and where the data is placed.

It is the largest piece of memory managed by the Java Virtual Machine and is shared by all threads.

Almost all object instances and data are allocated memory here in heap.

1.1 Heap Memory Logical Division

In order to perform efficient garbage collection, the virtual machine logically divides the heap memory into two areas :

  • Young Generation : for new objects and objects that have not reached a certain age;
  • Old Generation : for objects that are used for a long time.

The memory space of the old generation should be larger than that of the young generation.

Heap can be in a physically discontinuous memory space, as long as it is logically continuous, just like disk space.

1.1.1 Young Generation

The young generation is where all new objects start out.

It is divided logically into 3 areas : Eden, Survivor 0, Survivor 1.

The ratio of eden and survivor space is by default s0:s1:eden = 1:1:8.

It is possible to change this ratio, for instance, -XX:SurvivorRatio=6 will make s0:s1:eden = 2:2:6.

It has to use -XX:-UseAdaptiveSizePolicy along with -XX:SurvivorRatio to make the set up of ratio to be taken into account by JVM.

Most newly created objects are located directly in the Eden space.

When the Eden space is filled with objects and no space left for new objects, a garbage collection called Minor GC will be triggered.

Minor GC is applied in whole young generation, not only eden but also survivor space.

During minor gc, in eden space, all unreferenced objects will be removed, the rest referenced objects will be moved into survivor space with a mark of age = 1.

During minor gc, in survivor space, all unreferenced objects will be removed, the rest referenced objects will be copied from one survivor area into another survivor area with their marks of age increased by 1.

In survivor space, there is always one empty survivor area in order to be able to apply the copy gc algorithm.

Below is an example of 3 times of Minor GC in young generation.

Minor GC x 1 :

Minor GC x 2 :

Minor GC x 3 :

1.1.2 Old Generation

The old generation is where long-lived objects lie.

It is also called the tenured generation.

Basically if objects reach a certain age threshold after multiple Minor GC in the young generation, then they can be moved to the old generation.

In the above example, any surviving objects that have hit an age threshold of 8 cycles is moved to the old generation.

It is possible to use -XX:MaxTenuringThreshold to change the age threshold, its default value is 15.

If there is no space left in old generation, a Major GC will be triggered to clean up the objects in it.

Therefore, normally, a Major GC is triggered right after a Minor GC.

Some large objects may go directly into the old generation.

Those large objects are objects that require a lot of contiguous memory space.

Since no space left to put them directly into young generation even after a Min GC, they go directly to old generation.

1.2 Heap Memory Size

The size of the heap is determined when the JVM starts, we can set it by :

  • -Xms (-XX:InitialHeapSize) : the starting memory of the heap;
  • -Xmx (-XX:MaxHeapSize) : the maximum memory of the heap.

If the memory size of the heap exceeds the -Xmx maximum memory, OutOfMemoryError will be thrown.

It is recommended that the two parameters are configured to the same value in order to improve the performance of JVM.

By default, initial heap memory size = computer memory size / 64 and maximum heap memory size = computer memory size / 4 :

ovo@ovo:~$ java -XX:+PrintFlagsFinal -version | grep InitialHeapSize
   size_t InitialHeapSize                          = 262144000
ovo@ovo:~$ java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
   size_t MaxHeapSize                              = 4181721088
ovo@ovo:~$ free -b
               total        used        free      shared  buff/cache   available
Mem:     16721195008  5290176512  7070511104   488140800  4360507392 10597007360
Swap:     2147479552           0  2147479552

1.3 Monitor Heap Memory Usage

To monitor a running jvm’s heap memory usage, we can use multiple tools provided in jdk :

  • jmap :
ovo@ovo:~$ jhsdb jmap --pid 47584 --heap
Attaching to process ID 47584, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 21.0.3+7-LTS-152

using thread-local object allocation.
Garbage-First (G1) GC with 6 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4181721088 (3988.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 2508193792 (2392.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 22020096 (21.0MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 2097152 (2.0MB)

Heap Usage:
G1 Heap:
   regions  = 1994
   capacity = 4181721088 (3988.0MB)
   used     = 1080328 (1.0302810668945312MB)
   free     = 4180640760 (3986.9697189331055MB)
   0.02583453026315274% used
G1 Young Generation:
Eden Space:
   regions  = 0
   capacity = 20971520 (20.0MB)
   used     = 0 (0.0MB)
   free     = 20971520 (20.0MB)
   0.0% used
Survivor Space:
   regions  = 0
   capacity = 0 (0.0MB)
   used     = 0 (0.0MB)
   free     = 0 (0.0MB)
   0.0% used
G1 Old Generation:
   regions  = 1
   capacity = 241172480 (230.0MB)
   used     = 0 (0.0MB)
   free     = 241172480 (230.0MB)
   0.0% used
  • jstat :
ovo@ovo:~$ jstat -gc -h2 48543 1000
        S0C         S1C         S0U         S1U          EC           EU           OC           OU          MC         MU       CCSC      CCSU     YGC   YGCT     FGC    FGCT     CGC    CGCT       GCT   
        0.0         0.0         0.0         0.0      20480.0          0.0     235520.0          0.0        0.0        0.0       0.0       0.0      0     0.000     0     0.000     0     0.000     0.000
        0.0         0.0         0.0         0.0      20480.0          0.0     235520.0          0.0        0.0        0.0       0.0       0.0      0     0.000     0     0.000     0     0.000     0.000
  • jcmd :
ovo@ovo:~$ jcmd 48543 GC.heap_info
48543:
 garbage-first heap   total 258048K, used 3105K [0x0000000706c00000, 0x0000000800000000)
  region size 2048K, 2 young (4096K), 0 survivors (0K)
 Metaspace       used 133K, committed 320K, reserved 1114112K
  class space    used 8K, committed 128K, reserved 1048576K

1.4 Thread Local Allocation Buffers (TLABs)

TLAB is a region inside Eden, which is exclusively assigned to a thread.

According to the definition of TLAB, it is easy to think that it is where thread local variables are stored.

In fact, TLAB has nothing to do with thread local variables.

New objects are allocated in Eden area of young generation which is a memory space shared between threads.

If you take into account that multiple threads can allocate new objects at the same time, it becomes obvious that some sort of synchronization mechanism is needed.

The disadvantage of using synchronization mechanism is that it affects the allocation speed.

Here is where TLABs comes into play.

Each thread has its own TLAB, so only a single thread can allocate new objects in its TLAB.

Thanks to that, as long as objects are allocated in TLABs, there is no need for any type of synchronization, it is called fast allocation.

If a thread needs to allocate a new object that does not fit into the current TLAB (either TLAB is full or the object is too big), two cases can happen :

  • The thread’s current TLAB will not be used any more and the allocation is done in a new TLAB;
  • The allocation is done directly in Eden, since it is shared, that’s why synchronization is needed even if it comes at a price, it is called slow allocation.

The JVM decides what will happen based on flag -XX:TLABWasteTargetPercent which allows JVM to calculate a threshold.

When the size of object to be allocated is larger than the threshold, then JVM would use a slow allocation (case 2).

Otherwise, it retires the TLAB and allocates that object inside the new one (case 1).

By default, this threshold is equal to 1% of the TLAB size and is determined by the -XX:TLABWasteTargetPercent=N flag (The default value is 1).

As the number of slow allocations increases, in order to lower the chance of more slow allocations, JVM increments the TLABWasteTargetPercent by some value determined by the -XX:TLABWasteIncrement=N (Default value is 4).

For example, first slow allocation happens when the object is larger than 1% of the TLAB.

The second one happens, if it’s larger than the 5% of TLAB.

TLAB waste means when JVM allocates a large object outside of a TLAB, then the remaining space of that TLAB is wasted because of a slow allocation.

For example, if JVM allocates a 30KB object outside of a 100KB TLAB, when 75KB of it is full, then those remaining 25KB is wasted.

Below are some other TLABs jvm flags :

  • -XX:+UseTLAB : enable TLABs, by defalt, it is enabled, you can disable it by using -XX:-UseTLAB even if it is not recommanded at all;
  • -XX:TLABSize : set the initial TLAB size, by default, it is 0, it means that jvm calculate the initial size of each thread, it is not recommanded to take initiative to set TLABs initial size;
  • -XX:ResizeTLAB : let JVM adaptively resize the TLAB size, by default, it is enable.

2. Method Area

Like Heap, Method Area is also shared by all the jvm threads.

But it is just a concept defined in the JVM specification.

It is used to store data such as class information, constant pool, static variables, and JIT-compiled code.

It does not specify how to implement it.

Different manufacturers have different implementations.

The permanent generation (PermGen) is a concept unique to the Hotspot virtual machine.

It was replaced by the MetaSpace in Java 8.

Both the permanent generation and the metaspace can be understood as the implementation of the method area.

In this article, we will only focus on Hotspot virtual machine’s MetaSpace in java 8.

2.1 Method Area Size

Before jdk 1.8, PermGen (Permanent Generation) is a special area in jvm heap.

So the size of PermGen can not bigger than jvm heap max size.

With its limited memory size, PermGen is involved in generating the OutOfMemoryError.

Since jdk 1.8, PermGen has been replaced by Metaspace.

Metaspace is not inside jvm heap anymore, it uses native memory space.

Therefore, with this improvement, JVM reduces the chance to get the OutOfMemory error.

Below are common jvm flags for metaspace :

-XX:MaxMetaspaceSize=N

This flag limits the amount of native memory used for class metadata.

If it is not specified, the Metaspace will dynamically re-size depending of the application demand at runtime until there is no more native memory left.

If it is specified, when the metaspace memory usage is bigger than its value, there will be OutOfMemoryError: Metaspace.

-XX:MetaspaceSize=N

This flag specifies the standard value for full GCs that originate in the Metaspace area.

It is a threashold for resizing metaspace, every time there is resize, there is a full gc.

So if MaxMetaspaceSize is specified, it is recommanded to set MetaspaceSize = MaxMetaspaceSize.

If MaxMetaspaceSize is not specified, it is recommanded to set MetaspaceSize with a high value.

We can use jcmd to monitor the usage of metaspace :

yan@yan:~$ jcmd 11115 VM.metaspace
11115:

Total Usage - 3 loaders, 638 classes (623 shared):
  Non-Class:    5 chunks,      4,01 MB capacity,  140,00 KB (  3%) committed,   124,78 KB (  3%) used,    15,20 KB ( <1%) free,    16 bytes ( <1%) waste , deallocated: 0 blocks with 0 bytes
      Class:    4 chunks,    261,00 KB capacity,   69,00 KB ( 26%) committed,     8,32 KB (  3%) used,    60,68 KB ( 23%) free,     0 bytes (  0%) waste , deallocated: 1 blocks with 464 bytes
       Both:    9 chunks,      4,27 MB capacity,  209,00 KB (  5%) committed,   133,10 KB (  3%) used,    75,88 KB (  2%) free,    16 bytes ( <1%) waste , deallocated: 1 blocks with 464 bytes


Virtual space:
  Non-class space:       64,00 MB reserved,     192,00 KB ( <1%) committed,  1 nodes.
      Class space:        1,00 GB reserved,     128,00 KB ( <1%) committed,  1 nodes.
             Both:        1,06 GB reserved,     320,00 KB ( <1%) committed. 


Chunk freelists:
   Non-Class:

 16m: (none)
  8m:    2, capacity=16,00 MB, committed=0 bytes (  0%)
  4m: (none)
  2m:    2, capacity=4,00 MB, committed=0 bytes (  0%)
  1m:    2, capacity=2,00 MB, committed=0 bytes (  0%)
512k:    2, capacity=1,00 MB, committed=0 bytes (  0%)
256k:    2, capacity=512,00 KB, committed=0 bytes (  0%)
128k:    2, capacity=256,00 KB, committed=0 bytes (  0%)
 64k:    2, capacity=128,00 KB, committed=0 bytes (  0%)
 32k:    2, capacity=64,00 KB, committed=0 bytes (  0%)
 16k:    2, capacity=32,00 KB, committed=0 bytes (  0%)
  8k: (none)
  4k:    2, capacity=8,00 KB, committed=0 bytes (  0%)
  2k: (none)
  1k: (none)
Total word size: 23,98 MB, committed: 0 bytes (  0%)

       Class:

 16m: (none)
  8m:    1, capacity=8,00 MB, committed=0 bytes (  0%)
  4m:    1, capacity=4,00 MB, committed=0 bytes (  0%)
  2m:    1, capacity=2,00 MB, committed=0 bytes (  0%)
  1m:    1, capacity=1,00 MB, committed=0 bytes (  0%)
512k:    1, capacity=512,00 KB, committed=0 bytes (  0%)
256k: (none)
128k:    1, capacity=128,00 KB, committed=0 bytes (  0%)
 64k:    1, capacity=64,00 KB, committed=0 bytes (  0%)
 32k:    1, capacity=32,00 KB, committed=0 bytes (  0%)
 16k:    1, capacity=16,00 KB, committed=0 bytes (  0%)
  8k:    1, capacity=8,00 KB, committed=0 bytes (  0%)
  4k: (none)
  2k:    1, capacity=2,00 KB, committed=0 bytes (  0%)
  1k: (none)
Total word size: 15,74 MB, committed: 0 bytes (  0%)

        Both:

 16m: (none)
  8m:    3, capacity=24,00 MB, committed=0 bytes (  0%)
  4m:    1, capacity=4,00 MB, committed=0 bytes (  0%)
  2m:    3, capacity=6,00 MB, committed=0 bytes (  0%)
  1m:    3, capacity=3,00 MB, committed=0 bytes (  0%)
512k:    3, capacity=1,50 MB, committed=0 bytes (  0%)
256k:    2, capacity=512,00 KB, committed=0 bytes (  0%)
128k:    3, capacity=384,00 KB, committed=0 bytes (  0%)
 64k:    3, capacity=192,00 KB, committed=0 bytes (  0%)
 32k:    3, capacity=96,00 KB, committed=0 bytes (  0%)
 16k:    3, capacity=48,00 KB, committed=0 bytes (  0%)
  8k:    1, capacity=8,00 KB, committed=0 bytes (  0%)
  4k:    2, capacity=8,00 KB, committed=0 bytes (  0%)
  2k:    1, capacity=2,00 KB, committed=0 bytes (  0%)
  1k: (none)
Total word size: 39,72 MB, committed: 0 bytes (  0%)



Waste (unused committed space):(percentages refer to total committed size 320,00 KB):
        Waste in chunks in use:     16 bytes ( <1%)
        Free in chunks in use:     75,88 KB ( 24%)
                In free chunks:      0 bytes (  0%)
Deallocated from chunks in use:    464 bytes ( <1%) (1 blocks)
                       -total-:     76,35 KB ( 24%)

chunk header pool: 31 items, 9,02 KB.

Internal statistics:

num_allocs_failed_limit: 0.
num_arena_births: 6.
num_arena_deaths: 0.
num_vsnodes_births: 2.
num_vsnodes_deaths: 0.
num_space_committed: 5.
num_space_uncommitted: 0.
num_chunks_returned_to_freelist: 0.
num_chunks_taken_from_freelist: 10.
num_chunk_merges: 0.
num_chunk_splits: 6.
num_chunks_enlarged: 1.
num_inconsistent_stats: 0.


Settings:
MaxMetaspaceSize: unlimited
CompressedClassSpaceSize: 1,00 GB
Initial GC threshold: 21,00 MB
Current GC threshold: 21,00 MB
CDS: on
 - commit_granule_bytes: 65536.
 - commit_granule_words: 8192.
 - virtual_space_node_default_size: 8388608.
 - enlarge_chunks_in_place: 1.
 - use_allocation_guard: 0.

2.2 Method Area Storage

Method area stores the class structure information such as type, fields, methods, constant pool etc, and also code caches compiled by the JIT (just-in-time) compiler.

Type Information

For each loaded type (class, interface, enumeration, annotation), the JVM must store the following type information in the method area :

  • The full valid name (packagename.classname) of this type;
  • The fully valid name of the direct superclass of this type (there is no superclass for neither interface nor java.lang.Object);
  • The modifiers for this type (Access Modifiers : private, default, protected, public, Non-Access Modifiers : final, static, abstract, transient, synchronized, volatile);
  • The direct interfaces of this type if exists.

Fields Information

The information about all fields of the type and the order in which the fields are declared are also stored in the method area.

It includes : field name, field type, field modifiers.

Methods Information

The information about all methods of the type are also stored in the method area.

It includes : method name, method return type, method modifiers, number and type of method parameters, method bytecode instructions, operand stack, local variable table and size, exception table.

Runtime Constant Pool

In a valid bytecode file, there is constant pool which contains various literals and symbolic references to fields and methods generated during compilation.

This constant pool will be stored in the runtime constant pool of the method area after the class is loaded.

The runtime constant pool contains a variety of constants, including literals that are already known by the compiler, and method or field references that cannot be obtained until runtime.

At this stage, it is no longer a symbolic address in the constant pool, and it is replaced by a real address.

String Constant Pool & Static Variables

String constant pool and static variables of class are also parts of method area.

They are not in native memory but in jvm heap so that the unreferenced strings and static variables will be cleaned up during every GC.

Below is the interaction between stack, heap and method area when we declare a new instance of a specific class :