How is the memory region of the JVM divided?

Some of the JVM’s memory partitions are private to the thread, and some belong to the entire JVM process. Some areas will throw an OOM exception, while others will not. Understanding the JVM’s memory partition and characteristics is the basis for locating in-line memory problems. How are JVM memory regions divided?

The first is the Program Counter Register. In the JVM specification, each thread has its own Program Counter. This is a relatively small memory space that stores the ADDRESS of the JVM instructions for the Java method being executed by the current thread, the line number of the bytecode. This counter is empty if the Native method is being executed. This memory area is the only one that does not specify any OOM condition in the Java Virtual machine specification.

Second, the Java Virtal Machine Stack (Java Virtal Machine Stack) also belongs to the thread private area. Each thread creates a virtual Machine Stack when it is created, and its life cycle is consistent with that of the thread. When the thread exits, the thread’s virtual Machine Stack is also reclaimed. The virtual machine keeps a stack frame inside the stack, each method call will be pressed, the JVM on the stack frame only out of the stack and push two operations, the method call at the end of the stack operation.

This area stores local variation tables, basic types of data known at compilation time, object references, method exits and other information.

Third, the Native Method Stack is similar to the virtual machine Stack. The Native Method Stack is the Stack used when calling local methods. Each thread has a local Method Stack.

Fourth, the Heap. Almost all instances of Java objects created are allocated directly to the Heap. The heap is shared by all threads, and areas on the heap are further divided by the garbage collector, such as new generation and old generation. When a Java virtual machine starts up, you can specify the size of the heap area with a parameter like “Xmx”.

Fifth, Method Area. The method area, like the heap, is shared by all threads and stores Meta data loaded by the virtual machine, including class information, constants, static variables, and code compiled by the just-in-time compiler. The important thing to note here is that the runtime constant pool is also in the method area. According to the Java Virtual Machine specification, OutOfMemoryError is thrown when the method area cannot meet memory allocation requirements. Due to the early implementation of HotSpot JVMS, which extended CG generation collection to the method area, many would refer to the method area as permanent generation. Oracle JDK8 has removed permanent generations and added Metaspace.

Sixth, the run-time Constant Pool, which is part of the method area and is limited by the memory of the method area, will throw an OutOfMemoryError when the Constant Pool can no longer allocate memory.

In addition to the description of the Class’s version, methods, fields, interfaces, and so on, another piece of information in a Class file is the constant pool. The first four bytes of each Class file are called the Magic Number, which determines whether the file is acceptable to the virtual machine. The next four bytes store the version number of the Class file. Immediately after the version number is the constant pool entry. The constant pool mainly stores two types of constants:

  • Literals, such as text strings and final constant values

  • Symbolic references, which hold compile-related constants, need to be converted at run time to get the true memory entry address because Java does not have the concatenation process that C++ does.

A constant pool in a class file, also known as a static constant pool, is loaded into memory by the JVM after class loading and stored in the runtime constant pool.

Seventh, Direct Memory, which is not part of the runtime data area of the Java Virtual machine as defined by the Java specification. Java NIO can use Native methods to allocate memory directly out of the Java heap, using DirectByteBuffer objects as references to this out-of-heap memory.

The following graph shows the memory footprint of a running Java process:

In what areas can OOM occur?

According to Javadoc, OOM is when the JVM is out of memory and the garbage collector is unable to provide more memory. As you can see from the description, the garbage collector typically tries to reclaim memory before the JVM throws an OutOfMemoryError.

From the above analysis of the Java data area, except the application counter does not occur OOM, which area will occur OOM?

First, heap memory. Running out of heap memory is one of the most common reasons to send OOM. OutOfMemoryError is thrown if there is no memory in the heap to complete the allocation of object instances and the heap can no longer expand. The current mainstream JVM can control the size of the heap with -xmx and -xMS. OOM on the heap can be caused by memory leak or heap size allocation.

Second, the Difference between the Java virtual machine stack and the local method stack is that the virtual machine stack serves the Execution of Java methods for the VIRTUAL machine, while the local method stack serves the Native methods used by the virtual machine. They are the same in memory allocation exceptions. In the JVM specification, there are two exceptions for Java virtual machine stacks: 1. Throw StackOverFlowError if a thread requests a stack larger than the allocated stack size, such as a recursive call that will not stop; 2. 2. If the VM stack can be dynamically expanded and sufficient memory cannot be allocated during vm stack expansion, an OutOfMemoryError is thrown.

Third, direct memory. Direct memory is not part of the virtual machine’s run-time data area, but since it is memory, it is limited by physical memory. NIO, introduced in JDK1.4, uses Native libraries to allocate memory directly on out-of-heap memory, but can also result in OOM if direct memory is low.

Fourth, method area. As the Metaspace metadata, introducing method in OOM error message became the “Java. Lang. OutOfMemoryError: Metaspace”. In older Versions of the Oracle JDK, because the size of the permanent generation is limited, and the JVM is not active in garbage collection of the permanent generation, if you write data to the permanent generation, such as the string.intern () call, it takes up too much space in the permanent generation and runs out of memory, it will also cause OOM problems. The corresponding error letter as “Java. Lang. OutOfMemoryError: PermGen space”

What is the heap memory structure?

There are tools available to understand the memory contents of the JVM. What tools should be used to locate specific memory areas?

  • Graphic chemical tools. Graphical tools have the advantage of being intuitive. When connected to a Java process, they can display the usage of heap memory and off-heap memory. Similar tools include JConsole,VisualVm, and so on.

  • Command line tools. These tools can perform queries at run time, including Jstat, JMap, etc., and can view heap memory, method area, etc. These tools are often used to locate online problems. Jmap can also generate Heap Dump files, which on Linux can be pulled locally and analyzed using Eclipse MAT or JHAP.

More on memory monitoring and diagnostics later. Now for the next question: What is the structure inside the heap?

From the garbage collector’s point of view, memory can be divided into the new generation and the old generation. The rules for allocating memory depend on what combination of garbage collectors is currently in use, as well as the configuration of memory-related parameters. In the large direction, objects are preferentially allocated to the Eden region of the new generation, while large objects directly enter the old age.

First, the Eden area of the new generation, in which objects are allocated preferentially, and the JVM can allocate a private cache area for each Thread, called TLAB (Thread Local Allocation Buffer), to avoid the need for locking and other mechanisms to affect the Allocation speed. TLAB is allocated on the heap and is located in Eden. The structure of TLAB is as follows:

// ThreadLocalAllocBuffer: a descriptor for thread-local storage used by

// the threads for allocation.

//            It is thread-private at any time, but maybe multiplexed over

//            time across multiple threads. The park()/unpark() pair is

//            used to make it avaiable for such multiplexing.

class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {

  friend class VMStructs;

private:

  HeapWord* _start;                              // address of TLAB

  HeapWord* _top;                                // address after last allocation

  HeapWord* _pf_top;                             // allocation prefetch watermark

  HeapWord* _end;                                // allocation end (excluding alignment_reserve)

  size_t    _desired_size;                       // desired size   (including alignment_reserve)

  size_t    _refill_waste_limit;                 // hold onto tlab if free() is larger than this
Copy the code

In essence, TLAB management relies on three Pointers: start, end and TOP. Start and end mark the area managed by the TLAB in Eden, and this area will not be used by other threads to allocate memory. Top is the allocation pointer. At the beginning, it points to the position of Start. Therefore, the structure of Eden in memory is roughly as follows:

Second, the Survivor area of the new generation. When the Eden region runs out of memory, a Minor GC, also known as a generation GC, is triggered. Objects that survive the Minor GC are copied to the Survivor region. I think the Survivor zone’s role is to avoid triggering the Full GC too soon. If there is no Survivor, each Minor GC in Eden sends objects directly to the old age, which quickly runs out of memory and triggers a Full GC. There are two Survivor zones in the new generation, and I think the purpose of both Survivor zones is to improve performance and avoid memory fragmentation. At any given time, one Survivor is empty, and in the event of a Minor GC, Eden and the Survivor of another Survivor are copied into that Survivor to avoid memory fragmentation. The memory structure of the new generation is as follows:

Third, the old age. An object with a long lifetime is usually copied from a Survivor region, but when the object is too large for continuous memory storage in the new generation, the large object is allocated directly to the old generation. Generally speaking, common objects are allocated in TLAB. Large objects are directly allocated in other memory areas in Eden area, while large objects are directly allocated in the old age.

Fourthly, permanent generation. As mentioned earlier, in the earlier Hotspot JVMS there was the concept of old age, which was used to store metadata for Java classes, constant pools, Intern strings, and so on. After JDK8, the old age was removed and the concept of metadata areas was introduced.

Fifth, Vritual space. As mentioned earlier, you can use Xms and Xmx to specify the minimum and maximum size of the heap. If Xms is smaller than Xmx, the size of the heap is not directly expanded to the upper limit, but is reserved for allocation to new generations as memory requirements increase. The Vritual space is this part of the reserved memory area.

To sum up, the memory structure in the Java heap can be drawn as follows:

We can specify the size of the above heap memory area with a few parameters:

  • -Xmx value Specifies the maximum heap size

  • -Xms value Specifies the minimum initial heap size

  • -xx :NewSize = value Specifies the size of the new generation

  • -xx: NewRatio = value Size ratio of the old to the new. By default, the ratio is 2, which means that the old are twice as big as the new. [Fixed] Full GC takes a long time when older generations are too large If the age is too small, it is easy to trigger the Full GC, and the Full GC frequency is too high, which is the effect of this parameter.

  • -xx: SurvivorRation = value. Set the size ratio between Eden and Srivivor. If the value is 8, it means that a Survivor is 1/8 of Eden and 1/10 of the whole new generation.

What are the common performance monitoring and fault locating tools?

In system performance analysis, CPU, memory and IO are the main concerns. In many cases, service problems will be reflected in the three aspects, such as CPU surge, memory shortage, OOM, etc. At this time, you need to use corresponding tools to monitor performance and locate problems.

For CPU monitoring, you can first use the top command to view the load. Here is a screenshot of using top to view the load:

Load Average indicates the average system load of 1 minute, 5 minutes, and 15 minutes. Based on these three figures, you can determine whether the system load is large or small. When the CPU is completely idle, the average load is 0. When the CPU workload is saturated, the average load is 1. Therefore, the lower the three values of Load Average are, the lower the system load is. Then when can we see that the system load is heavy? Understanding Linux CPU Load — When Should You Be worried If there is only one CPU in the computer, think of the CPU as a one-way bridge with only one lane on it, and all the cars have to cross that bridge. then

The system load is 0, which means there are no cars on the bridge

The system load is 0.5, which means there are cars on half of the bridge

System load 1 means that the road on the bridge is already occupied by cars

The system load is 1.7, which means that the vehicles on the bridge are full (100%) and there are still 70% vehicles waiting to cross the bridge:

From the screenshot of the top command, you can see that the load average of these three value machines is very low. If these three values are very high, say more than 50% or 60%, they should be noticed. In terms of time, if you see a slow increase in CPU load, you should also be alert.

Other performance monitoring tools, such as memory and CPU, are shown in a brain map:

For details, see Thinking about Java Fault Locating from an Online Fault.