The introduction
The previous article focused on the JVM subsystem analysis, has been detailed in the virtual machine class loading subsystem and the execution engine subsystem, but this article is going to the JVM runtime memory area and the JVM runtime memory overflow and leakage issues are a comprehensive analysis.
A comprehensive overview of the JVM runtime memory region
JVM is running a Java program, they put their own management of memory can be divided into several different data areas, these areas have their own respective purposes, at the same time, the different regions also have different life cycles, some areas with the start of the virtual machine set up, with the end of the virtual machine is destroyed, some of the area is in the running process of continuous creation and destruction.
The JVM memory area, also known as the JVM runtime data area, mainly containsProgram counters, virtual machine stack, local method stack, heap space, metadata space (method area), runtime constant pool, string constant pool, direct memory (local memory), and so on. Standing in the program execution point of view, the overall can be divided into thread shared area and thread private area two large. The diagram below:
Each memory region of the JVM is described in terms of thread ownership and thread sharing.
1.1. Thread Private areas
Thread private areas are defined as areas allocated by the JVM for each thread when they are created, and the life cycle of these memory areas is created and destroyed as the thread starts and dies. Once these regions are created, other threads are not visible and only the current thread itself can access them.
The thread private areas in the runtime data area mainly include: program counters, virtual machine stacks, and local method stacks.
1.1.1 Progran Counter Register
A program counter is a small area that the JVM creates for each thread, where each thread has one and only one program counter, and the threads do not interfere with each other. The life cycle is the same as that of a thread, starting it and ending it when it is destroyed. It is also the only memory region of the JVM that does not have an OOM (OutOfMemoryError/ memory overflow) and is not touched by GC.
When a thread executes a Java method, it records the address of the bytecode instruction being executed by the thread. When the execution engine finishes processing an instruction, the program counter needs to be updated to change the pointer to the address of the next instruction to be executed. The execution engine will execute the corresponding instruction according to the address recorded in the PC counter. The PC counter is null when the thread is executing some Native method written in C/C++. In addition to this, it can also ensure that the thread can be restored to the correct position after the CPU time slice switch.
1.1.2 VM Stack
The virtual machine stack, also known as the Java stack, is mainly used as the unit of execution in the MEMORY area of the JVM. The stack is responsible for how the program is executed and how the data is processed. The lifecycle is consistent with threads, with each thread being created with a virtual stack for it.
When a thread executes a Java method, it will produce a Stack Frame for the method executed. Each Java method is called to the end of execution, corresponding to the process of a Stack Frame in the virtual machine Stack from the Stack to the Stack. How much memory space should be allocated for a Stack Frame has been determined by the compiler. It is not affected by the size of the runtime variable data. For the execution engine, it only operates on the stack frame element (called the current stack frame) at the top of the stack, and the method associated with the current stack frame is called the current method.
A stack frame mainly contains local variable table, operand stack, dynamic link, method exit and other information, and then they are analyzed in turn.
1.1.2.1 Scale of local variations
A local variable table is an array of slots used to store the reference information of the current instance object, method parameters, basic data type variables defined in the method body, object references, and return addresses. Max_locals in the Code property of the method table of the Class file specifies the maximum size of the local variable table required by the method.
Slot: A Slot is the smallest unit in a local variable table. The specified size is 32 bits. Data with a size of 32 bits, such as variables of the int type and object references after Pointers are compressed, are stored in a Slot. The JVM allocates two consecutive slots for 64-bit data, such as long, double, and object references that do not have pointer compression enabled.
Each slot in the local variable table will have a fixed index subscript value. When executing a method, the execution engine will access the specified slot in the local variable table according to the index value, and then load the data into the operand stack for execution.
The data stored in the local variable table is valid only for the current method. The VM executes operations based on the operand stack and data stored in the local variable table. After the method completes, the local variable table is destroyed with the unstack/destruction of the stack frame. In general, if the current method is a constructor or instance method, the subscript in the local variable table of those methods is0
The slots of thethis
References, that is, the first place in the local variable table is used to put the object reference of the current method, and other local variables are stored in the local variable table in order. The diagram below:
PS: It is worth noting that the slot space in the local variable table can be reused. When a data in the local variable table loses its effect and the reference relationship is not maintained, the virtual machine tries to allocate new data to the slot where the data was stored. Let’s take a case to understand:
public void test(a){
int a = 1;
long b = 8l;
Object obj = new Object();
// Simulate the process using the above variables....
obj = null;
// Proceed to......
int c = 7;
/ /...
}
Copy the code
In the above code, we can preliminarily imagine the layout of the initial local variable scale according to the previous explanation of the local variable scale, which should look like the following:
According to the previous code, the local variable table after the initial allocation should be as shown in the above figure. According to the original logic,int
Type variablec
, should be assigned to the sixth slot, whose index is5
But actually because we are in the Java program above, yesobj
Variables are set to null, representing local variables stored in the tableobj
The referenced slot is no longer used, so the virtual machine tries to reuse the slot as follows:
When a variable of integer type is requiredc
When assigning slots, the system directly assigns thec
Allocated to the fifth slot, which is the original storageobj
The position of the reference pointer. However, it is worth noting that this is a direct replacement of the original slot data, rather than removing the original slot data first.
The object reference information in the local variable table is an important GC root node in the subsequent GC chapter. As long as an object in the heap is referenced directly or indirectly in a local variable table, the GC trigger will not reclaim the heap object.
Also, in terms of performance tuning, the part of the stack frame that is most closely associated with it is the local variable table, which is used by the virtual machine to complete the method delivery during method execution.
1.1.2.2 Operand Stack
The operand stack is a stack structure that follows the FILO first-in-last-out pattern. The max_stacks attribute in the Class file structure definition defines the maximum stack depth during execution (the maximum stack depth for a method is determined at the compiler). As mentioned more than once in previous chapters, the Java VIRTUAL machine is a stack-based virtual machine, and the interpreter in the execution engine works on the stack, which is the operand stack.
When a method is executed, a stack frame corresponding to the method is first created. The stack of operands in this stack frame is initially empty. During execution, data is written (pushed) and fetched (pushed) to the stack according to bytecode instructions. The main purpose of the operand stack is to hold the intermediate results of the calculation and to serve as temporary storage space for variables during the calculation.
Like the previous local variable table, the operand stack is also composed of a 32-bit byte array. The main data types that can be stored in the operand stack are: Int, long, float, double, Reference, and returnType. Data of the byte, short, and char types will be converted to int and stored on the stack before being pushed.
But different from the local variable table: the local variable table accesses the stored data through subscript index, while the operand stack accesses the data through the standard push and push way.
At the same time as the operand stack at runtime is located in memory, often go to read and write operations will affect the execution speed of the memory, so in the process of execution, the actual elements of virtual opportunities will stack cache to all of the physical CPU registers or cache (L1 / L2 / L3), in order to reduce the number of memory, speaking, reading and writing, so as to improve execution engine execution efficiency.
I’m going to go back to the previous cantoadd
methodsa+b
Examples to explain, source code and operand stack calculation process as shown below:
1.1.2.3. Dynamic Linking
Each stack frame in the virtual machine stack contains a reference to the method that the stack frame belongs to in the run-time constant pool, and this reference is held to support dynamic linking during method invocation (such as invokeDynamic instructions).
When a Java source file is compiled into a Class file, all variables and method calls in the Class are converted into symbolic references, which are then stored in the constant pool of the Class file. When a Class file describes a method calling another method, symbolic references to the method in the constant pool are used. The purpose of dynamic linking is to convert these symbolic references into direct references to the calling method.
Constant pool: in the class bytecode file generated after compilation.
Runtime constant pool: located in the runtime metadata space/method area.
1.1.2.4. Return Address
When a method is first executed by the execution engine, only two conditions will cause the method to exit. One is when a bytecode instruction (iReturn, lreturn, dreturn, areturn, return) is normally returned during execution.
ireturn
: The return value isInt, byte, char, short, Boolean
Type is returned using this directivelreturn
: The return value islong
Type is returned using this directivedreturn
: The return value isdouble
Type is returned using this directiveareturn
This directive is used when the return value is a reference typereturn
: there is no returnvoid
, class, or interface initialization method
The exit of a method after its normal execution is called the normal completion exit, and the program counter of the caller is used as the return address when the returned bytecode instruction is executed.
In addition to exit after normal execution, there is another case that can also cause the exit of a method. That is, an exception occurs during the execution of a method and the exception is not handled in the method body (no try/catch). This case is called exception completion exit. The return address needs to be determined by the exception handler table.
When a method exits at the end of execution, the following steps are performed:
- ① Restore the local variable table and operand stack of the upper method.
- ② If the current method has a return value, push the return value into the operand stack of the caller’s method frame.
- ③ Change the address of the PC counter to the position of the next instruction in the method so that the caller can work normally.
- PS: In the case of an exception exit, no value is returned to the upper caller.
1.1.2.5. Additional Information
When implementing the JVM, vendors add information to the stack frame that is not described in the VIRTUAL Machine Specification, such as debugging information. Information that is not described in the specification is called additional information (additional information that may exist for different VMS may not be consistent).
1.1.2.6 Features and operating principles of virtual machine stack
The array is a fast and effective storage method, and it is also placed in memory at runtime, and the top data of the operand stack will be put into the cache or register, so in terms of access speed, it is second only to the PC register.
There is no garbage collection in the memory area of the virtual machine stack, but there is OOM. In the Java Virtual Machine Specification, there are two exceptions for this area:
StackOverflowError
: This exception is raised when the stack depth of the current thread request is greater than the depth allowed by the virtual machine stack.OutOfMemoryError
: Will be thrown if sufficient memory cannot be allocated during expansionOOM
The exception.
The vm stack size for each thread can be adjusted using the -xss parameter. The default unit is byte. The default size is 1MB, 1024KB, or 1048576 bytes.
During the running of the JVM, each thread has its own independent virtual stack (thread stack). Data in the current thread stack is stored in the format of stack frames. Each method being executed by the current thread generates a corresponding stack frame in the virtual stack, as shown in the following example:
public void a(a){
int b_result = b();
}
public int b(a){
c();
return 9;
}
public void c(a){
/ /...
}
Copy the code
When a thread executes a methoda()
, its virtual machine stack is as follows:
For this thread, only one active stack frame will exist at the same time for all stack frames in the stack, that is, the stack frame at the top of the stack, that is, the current stack frame. Execution engine executes, it will only perform the current stack frame bytecode instruction, if the execution of the current method, in which the calls to other methods, then another method corresponding to the stack frame will be created, on the top, to become the new current frame, then the execution engine can be carried to a new frame, when the frame at the end of the execution, will back to before the implementation of this method results to a stack frame, That is, the upper level caller, as in the case abovea()
isb()
The virtual machine then discards the current stack frame, causing the previous stack frame to become the current frame at the top of the stack again. This process is repeated until a method call chain ends or is interrupted by an exception.
1.1.3 Native Method Stack
The Native method stack is similar to the virtual machine stack, except that the virtual machine stack is used to execute Java methods, while the Native method stack is used to execute Native methods written by C. At the beginning of the program running, the Native method will be registered in the local method stack, and the relevant data (parameters, local variables, etc.) of the local method will be saved when the execution engine executes.
Since Native methods are written in C, Native methods in the local method library will be compiled into programs based on the Native hardware and operating system. Local method execution is performed in the OS, not in the JVM, so the OS’s program counter is used instead of the JVM’s program counter. When you start executing a local method, you enter an environment that is no longer limited by the virtual machine, at the same level that the virtual machine has direct access to any memory area of the JVM. You can also use the CPU processor’s registers and local memory directly. The local method stack simply stores the information necessary for the thread to run the method, such as exits, entrances, dynamic links, local variables, operand stacks, etc.
However, in the HotSpot VIRTUAL machine, it combines the local method stack with the virtual machine stack.
1.2. Thread Sharing area
Thread sharing means that at run time, these areas are visible to all threads in the program, their state does not change with the death of a single thread, and they are created at the same level as the JVM and live symbiotically throughout the JVM’s life cycle.
Thread sharing in the runtime data area mainly consists of three large blocks: heap space, metadata space (method area) and direct memory.
1.2.1 Java Heap Space
Heap space is also the most important area of Java memory, and most JVM tuning efforts are based on it. The function of the Java heap is different from the previous analysis of the Java stack. The stack is mainly used as a unit of runtime to temporarily store the data needed and generated during runtime, while the Java heap is a unit of storage. The main problem to solve is the data storage problem, focusing on the field is how to store data, where, how to put and so on.
The heap space is created when the JVM starts. For the JVM, the heap space is unique. There is only one heap space per JVM, and the size is determined at creation time. An OOM is thrown if the -xmx parameter is larger than the specified size.
By default, if the heap size is not forced by a parameter, the JVM ADAPTS to the current platform, starting at 1/64 of the current physical machine memory, and maximum at 1/4 of the current physical machine memory.
When a Java program runs, most of the instance objects and array objects generated during the system run are stored in the heap.
When you create a Java heap, you don’t essentially allocate an entire space in memory directly to the JVM, because the Java Virtual Machine Specification states that the heap space can be physically discontinuous and only needs to be treated logically as contiguous. So a JVM’s heap space on the actual machine memory can be made up of space in several different locations in the machine memory, as shown below:
The Java heap is also an area that changes frequently, and the heap space varies from Java version to Java version:
- JDK7 and before: The heap space contains the new generation, the old generation, and the permanent generation.
- JDK8: The heap space contains the new generation and the old generation. The permanent generation is changed to the metadata space and sits outside the heap.
- JDK9: Heap space logically preserves the concept of generation, but physically does not have generation itself.
- JDK11: Heap space is logically and physically nongenerational from now on.
In essence, it is not the Java version that affects the heap space structure. The Java heap structure is dependent on the garbage collector used by the JVM at run time, and the GC determines how the heap space is divided at run time.
In JDK1.8 and earlier versions of Java, almost all GCS split the heap space into at least two regions: the new generation and the old generation, but in JDK1.9 and later, most GCS have started to do this generation-neutral way (for reasons discussed later).
1.2.1.1. Generational heap space
Generation refers to whether the heap space is divided into different regions for storing object instances with different life cycles during JVM running. Prior to JDK1.8, the heap structure is fully generational, i.e. logical + physical partitioning. Physical memory is divided into several different regions at runtime. In other words, there is one Eden area, two Survivor areas (Form/To areas) and one Old area. In terms of physical memory, each area is complete and continuous memory, and each area is used To store object instances of different cycles without interfering with each other.
1.2.1.2, regardless of generation heap space
In JDK1.9, G1 was introduced as the default GC embedded in the JVM, and the concept of generation-free Java heap space was introduced. However, there are two types of generation-free Java heap space: logical generation-free and physical generation-free, and logical + physical generation-free.
Logical generation, physical non-generational (G1) : The idea of logical generation still exists in object allocation, but the physical memory is no longer divided into several complete generational Spaces.
Logical + physical are non-generational (ZGC, ShenandoahGC) : there is no concept of generational, either logically or physically allocated memory for objects.
The following is a brief description of the different versions of the heap space structure, which will be explained in the GC chapter.
1.2.1.3, JDK7 and before heap space memory partition
In JDK1.7 and older JVMS, all GCS are physically and logically generalised, including the inline default GC Parallel insane + Parallel Old, so the heap space is typically split into three sections: the new generation, the Old generation, and the permanent generation:
- The New generation: one
Eden
Area, two of themSurvivor
Area (Form/To
District), proportion:8:1:1
- Elder generation: One
Old
area - Permanent generation: method area
The new generation is mainly used to store objects that have not met the allocation conditions of the old generationEden
Area is dedicated to storing newly created object instances, twoSurvivor
The zone is mainly used to “shelter” the surviving object during garbage collection.
The tenured generation is used to store object instances that meet the allocation conditions, such as objects that have reached the “age” and large objects that are too large.
The method area/permanent generation is used to store metadata information of a class, such as class description information, field information, method information, static variable information, exception table, method table, etc.
By default, the Ratio between the new generation and the old generation is 1:2, with the new generation accounting for 1/3 and the old generation accounting for 2/3. Of course, you can specify the Ratio by using -xx :NewRatio=x. You can also specify the maximum memory size of the new generation by using -xmn.
In the new generation, the default ratio of one Eden zone and two Survivor zones (Form/To zones) is 8:1:1. Of course, this ratio can be adjusted by using -xx :SurvivorRatio. The actual initial value is 6:1:1 because the JVM has adaptive mechanisms, which can be turned off with the -xx: -useadaptivesizePolicy argument (not recommended).
1.2.1.4 JDK8 heap space memory partition
By JDK1.8, the JVM consolidated the permanent generation, or method area, into metadata space and moved it out of the heap, placing it in local memory outside the heap space.
JDK1.8 when do not have what good say, and 1.7 the gap is not big, the biggest difference is that removed the method, add the metadata in local memory space to store the method area before most of the data (the data in the original method area not all were moved to yuan storage space, some data is scattered into the JVM the area). In addition, the constant pool was moved out of the heap in 1.8.
1.2.1.5 JDK9 heap space memory division
In JDK1.9, the heap space slowly began to change dramatically. Until then, the layout of the heap space was generational storage, both logically and physically. But by Java9, because the default GC was changed to G1, the memory regions in the heap were divided upRegion
Area.
In JDK1.9, G1 divided the Java heap into independent, equally-sized onesRegion
Area, but inHotSpot
The source ofTARGET_REGION_NUMBER
Defines theRegion
The number of zones is limited to2048
More than this is actually allowed, but after that, the heap space becomes unmanageable.
Generally, the size of a Region is equal to the total size of the heap space divided by 2048. For example, if the total size of the heap space is 8GB, that is 8192MB/2048=4MB, then the final size of each Region is 4MB. It is also possible to use -xx :G1HeapRegionSize to force the size of each Region, but it is not recommended because the default calculation method is best for managing the heap space.
G1 retains the concept of young and old generations, but they are no longer physically separated; they are collections of regions that can be separated from physical memory.
By default, the initial proportion of the new generation to the heap memory is 5%. If the heap size is 8GB, the young generation occupies about 400MB of memory, which corresponds to about 200 regions. You can set the initial proportion of the new generation by using -xx :G1NewSizePercent. The JVM keeps adding more regions to the new generation as Java programs run, but the maximum number of new generations does not exceed 60% of the total heap size. This can be adjusted by -xx :G1MaxNewSizePercent. Easy to trigger global GC). The ratio of Eden Region and Survivor Region in the New generation is the same as before, the default is 8:1:1. Assuming that there are 400 regions in the new generation, the ratio of the whole new generation is Eden=320,S0/From=40,S1/To=40.
In G1, the promotion conditions of the aged generation are the same as before. Objects that reach the age threshold will be transferred to the Region of the aged generation. The difference is that large objects are not allowed to enter the aged generation in G1. If an object is determined to be a large object, it is placed directly into the Humongous store.
In G1, the method to determine whether an object is large is as follows: The size of an object exceeds 50% of a common Region. If the size exceeds 50%, the current object is large, and the object is directly added to the Humongous Region. For example, the current size of the heap is 8GB, and each Region is 4MB. If the size of an object exceeds 2MB, it is considered as a large object.
The significance of Humongous zone: It can avoid some “short-lived” giant objects directly entering the old generation, save the memory space of the old generation, and effectively avoid GC overhead of the old generation due to insufficient space.
When global GC(FullGC) occurs in the heap space, Humongous region is also recycled in addition to the new generation and old generation.
1.2.1.6 JDK11 heap space memory partition
In JDK11, Java introduced a new garbage collectorZGC
, it is also based onRegion
Memory layout of the GC, this GC is truly generation-insensitive, both logically and physically.
In ZGC, the heap space is also divided into sectionsRegion
Region, but in the ZGCRegion
There is no concept of generation, it is simply a collection of allRegion
It is divided into three grades: large, medium and small:
- small
Region
Area (Small
) : Fixed size is2MB
Is used to assign less than256KB
The object. - medium
Region
Area (Medium
) : Fixed size is32MB
, used to allocate>=256KB ~ <=4MB
The object. - large
Region
Area (Large
) : There is no fixed size, the capacity can change dynamically, but the size must be2MB
Integer multiple of, used specifically for storage>4MB
Is a huge object. But it’s worth mentioning: everyLarge
The area can only hold one big object, which means how big your big object is, so thisLarge
So in general,Large
The area capacity is less thanMedium
And need to pay attention to:Large
The area space will not be redistributed (GC chapter details).
PS: In fact, the ZGC in JDK11 is not designed to abandon the idea of generational heap space, because in fact the idea of generational heap space was proposed in the first place essentially because of the concept of “most objects die overnight”, and in fact most Java programs conform to this phenomenon at runtime. So logical generation + physical generation is the best structure scheme for heap space. But the question is: why doesn’t ZGC design a generational heap space structure? In fact, the essence of the reason is that the generation implementation is very troublesome and complex, so we first implement a relatively simple and usable single-generation version, which may be optimized and improved in the future (but in fact, it is not clear whether the improvement will succeed. The head of ZGC’s R&D team, Per, came from JRockitGC group, and R University talked with Per: Per has tried four or five times before with JRockitGC and failed, but whether it will work with ZGC remains to be seen).
1.2.1.7 Heap summary
The Java heap space is the largest portion of the JVM runtime memory region, whose sole purpose is to store instances of objects created at runtime. Also, depending on the GC used at run time, the Java heap can be divided into different structures, mainly generational and non-generational. In contrast, generational structures are best suited to the “live and die” nature of Java objects. If the heap structure is generational, it allows the JVM to better manage objects in heap memory, including memory allocation and reclamation.
1.2.2 Local Memory
The local memory in the runtime data area can be divided into two main parts, one is the metadata space (the original method area) and the other is the direct memory. When running a process on any platform, the operating system allocates memory for it, and the JVM does the same, requesting resources (memory, CPU, thread count, etc.) from the operating system at startup. However, it is important to note that the metadata space and direct memory are not in the memory allocated by the OS for the JVM. Instead, the data is stored directly in the physical machine memory, but the local memory is still managed by the JVM.
1.2.2.1 Metaspace
As mentioned earlier, the metadata space was moved from the previous method area (permanent generation), so let’s talk about JDK1.7’s method area before we talk about metadata space.
The Method area is also known as permanent generation/persistent generation. The Method area mainly stores all data that can be obtained through reflection mechanism, such as Class information, Method Method information and Filed field information. How much space the Method area needs depends on how many classes are loaded when the JVM runs. The class-loaded Class file generates metadata for the Class and stores it in this area. Of course, when a class is unloaded, the space occupied by that class’s data is also released when FullGC occurs.
The method area mainly stores data: class metadata, VM internal tables, class hierarchy information/method information/field information, method compilation information and bytecode data, static variables, constant pool, and symbolic references. In JDK1.7, the default maximum size of the method area is 64MB, which can also be adjusted with the -xx :MaxPermSize parameter.
Why does JDK1.8 remove the method area? In JDK1.7, the string constant pool from the method area was moved to the heap. In 1.8, the method area was removed entirely for three reasons: ① The method area is not easy to set the size, give a large waste of space, give a small easy OOM, for example, Tomcat deployment of multiple projects, loading a large number of JAR packages is easy to cause the method area OOM. ② The garbage collection mechanism is inefficient for permanent generation collection and introduces some unnecessary complexity to GC. (3) In order to better integrate Sun HotSpot and BEA JRockit, the concept of method area is only found in HotSpot, and it does not exist in other virtual machines. Therefore, in order to better “future” of Oracle HotSpot, we have removed the method area. So as to achieve the perfect fusion of Sun HotSpot and BEA JRockit.
OK, after a brief look at the description of the method area, you can move on to the metadata space. Of course, if you want to know what’s stored in a specific method section, look at this.
Metadata space is the product of removing the method area in 1.8. It is mainly used to store runtime constant pool and class information, as follows:
The string constant pool in the run-time constant pool of the method area is placed in the heap. As the running time of the program increases, the string constant pool will become more and more strings and occupy more and more space. Therefore, the advantages of putting it in the heap are as follows: Make the string constant pool within the scope of the GC mechanism, and the string will also be recycled.
In addition to the open string constant pool being moved to the heap, the static variables of the class are also stored in the heap. The comparison is as follows:
1.2.2.2 Direct memory
Direct memory This area is not the memory area of a VM and is not defined in the Java VIRTUAL Machine Specification. When a VM is created, it directly requests memory space from the OPERATING system (OS). Therefore, it is an area that directly uses physical memory and is also called out-of-heap space.
Java NIO allows Java programs to directly use local direct memory to store data buffers, because if some file data is converted to objects and stored in the heap, It is easy to cause heap space to be overloaded and OOM. Therefore, for the sake of performance and stability, direct memory can be used for frequent read and write scenarios or scenarios where large files are read/written.
Direct memory can be created using java.nio.byteBuffer. AllocateDirect can be used to request direct memory, and direct memory can be operated using DirectByteBuffer stored in the heap.
The maximum direct memory size can be set using -xx :MaxDirectMemorySize. If this parameter is not specified, the maximum direct memory size is the same as that set by -xmx. Direct memory is a relatively expensive resource, because it needs to apply directly to the OS, so the allocation cost is high, and it is not under the direct control of THE JVM after it is created, so the GC mechanism is difficult to manage the memory space in this region, and only when FullGC occurs, this region will be reclaimed.
At the same time, the area is also can appear OOM, because of the physical machine memory is limited after all, is limited by hardware, so if you have been applied to the operating system for direct memory usage, afterward the JVM GC mechanism cannot effectively recycling used memory, probably will be in the lead up to the next FullGC physical machine allocated memory space application is run out, Trigger OOM.
In C, we can write a method to reclaim the direct memory, and then use it to reclaim the requested memory manually. The method is as follows:
import java.nio.ByteBuffer;
import sun.nio.ch.DirectBuffer;
public class NonHeapGC {
public static void clean(final ByteBuffer byteBuffer) {
if(byteBuffer.isDirect()) { ((DirectBuffer)byteBuffer).cleaner().clean(); }}public static void sleep(long i) {
try {
Thread.sleep(i);
}catch(Exception e) {
/*skip*/}}public static void main(String []args) throws Exception {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 200);
System.out.println("start");
sleep(5000);
clean(buffer);// Perform garbage collection
// System.gc(); // Perform Full GC for garbage collection
System.out.println("end");
sleep(5000); }}Copy the code
When the allocated memory is used, the clean() method can be manually called to reclaim the memory.
OOM(OutOfMemory)
OOM memoryError Refers to an OutOfMemoryError. The JVM runtime data area is at risk of running out of memory except for open program counters, as illustrated below.
2.1 Java heap Space OOM
The Java heap space is the area of memory used to store object instances and array data, and the JVM’s GC mechanism focuses on memory management for this area. However, if all the objects in the heap are still alive when out of memory GC occurs, and there is not enough memory to allocate new object instances, the heap space will end up in OOM, as shown in the following example:
public class OOM {
// Test the object class for memory overflow
public static class OomObject{}
/ method to test the Java heap space OOM * * * * JVM startup parameters: - Xms10M - Xmx10M - XX: + HeapDumpOnOutOfMemoryError * * /
public static void HeapOOM(a){
List<OomObject> OOMlist = new ArrayList<>();
// infinite loop: repeatedly adding object instances to the collection
for(;;) { OOMlist.add(newOomObject()); }}public static void main(String[] args){
// Call the method to test heap space OOMHeapOOM(); }}Copy the code
The HeapOOM method is used to add OomObject instances to the collection, and the HeapOOM method is used to add OomObject instances to the collection. The HeapOOM method is used to add OomObject instances to the collection.
– XX: + HeapDumpOnOutOfMemoryError: can make the virtual machine when abnormal memory Dump heap memory runtime snapshots, you can use VisualVM heap snapshot analysis (subsequent GC chapters are used, this does not make detailed introduction).
The final program execution results are as follows:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid16160.hprof ...
Heap dump file created [14045343 bytes in 0.092 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
.......
Copy the code
To the above results, can clearly see the Java. Lang. OutOfMemoryError: Java heap space this line information, can learn from this line of information: the current program execution of memory, and overflow area for the Java heap space.
2.1.1 Reasons for OOM in online environment heap Space
- ① The amount of data loaded in the memory is too large, resulting in OOM. For example, querying tens of millions of data in the database at one time leads to creating a large data array.
- ② There are references to objects in the collection object, so that some invalid objects in the collection cannot be collected by GC.
- ③ There are logically incorrect loops in the code that result in a large number of duplicate object instances in certain situations.
- ④ There are bugs in third-party dependencies, resulting in a large number of objects generated during runtime.
- ⑤ When the JVM starts, the heap space allocated for it with parameters is too small, resulting in insufficient memory for normal operation of the program.
- ⑥ There are infinite recursive calls in the program, resulting in the creation of the object OOM.
- ⑦ System traffic exceeds the original estimate, resulting in a large number of requests into the system, create a large number of objects, memory is too small OOM.
- Today…
In fact, there are many reasons for the Java heap OOM in the online environment, but in the end there are only a few: first, the program is running properly, there are too many live objects in the heap to recycle, and there is no memory allocation for new objects. 2. There is non-standard syntax in the code, and OOM appears during the operation due to code reasons, such as infinite recursion/endless loop/not released after use, etc. 3. Memory leakage occurs during operation, which eats away the memory bit by bit, resulting in the final available memory becoming very small, thus triggering OOM.
2.1.2 Troubleshooting of OOM problem in online environment heap
Generally speaking, when problems occur in the online environment, there are always several fixed steps, starting from problem discovery, and gradually moving to follow-up troubleshooting, locating, solving, trying the optimal solution, and properly considering extensibility. This is a complete link to solve the problem.
Problems, such as the heap space in front of the OOM after problems, first of all should be related to some JVM tools, to dump analysis of log, pinpoint the problems may occur several suspicious position, then the position of illness in turn, the final positioning to concrete is due to what reason lead to OOM, again “suit the remedy to the case”, Heap OOM problem generally has the following solutions:
- ① If it is determined that the code is faulty, use the tool to locate the specific code, and then correct the code.
- ② If it is true that the allocated heap space is no longer sufficient for the JVM to run properly, then more heap space should be allocated.
- ③ If OOM is caused by memory leak, further locate the cause of memory leak and take corresponding measures.
2.1.3, GC overhead limit exceeded
In the execution of a Java program, if when the JVM has spent more than 98% of the time in GC, but successfully recovered memory less than 2%, and the movement repeat five times, will be thrown. Java lang. OutOfMemoryError: GC overhead limit exceeded the mistake, In this case, the allocated space is not enough to support the normal overhead of the system, causing the program to use up all the memory resources that the GC mechanism can’t reclaim. In this case, you can try to increase the heap memory first.
2.2 Vm Stack and Local Method Stack OOM
The memory overflow of Java stack can be divided into local method stack and VIRTUAL machine stack OOM, but in HotSpot, the two are integrated, so there is only the problem of VIRTUAL machine stack OOM in the VIRTUAL machine stack, but in addition to the problem of virtual machine stack OOM, there is also another kind of memory problem: SOF, as follows:
StackOverflowError
: This exception is raised when the stack depth of the current thread request is greater than the depth allowed by the virtual machine stack.OutOfMemoryError
: Will be thrown if sufficient memory cannot be allocated during expansionOOM
The exception.
2.2.1 Problem test of VIRTUAL machine stack SOF
Code first:
public class OOM {
JVM startup parameter: -xss128k */
public static void VMStackSOF(a) {
int stackLength = 1;
stackLength++;
VMStackSOF();
}
public static void main(String[] args){
// Call the method to test the SOF of the virtual machine stackVMStackSOF(); }}Copy the code
In the example above, we used -xss to specify the size of the virtual machine stack to be 128KB, and then recursively called ourselves in the VMStackSOF() method. The result is as follows:
Exception in thread "main" java.lang.StackOverflowError
.........
Copy the code
Can see clearly from the result SOF problem, because the front by the virtual machine parameter setting for each thread stack space is 128 k, so in VMStackSOF () method of recursive, raise the Java program eventually. Lang. StackOverflowError errors. During execution, a thread executing a method, whether the stack frame is too large or the virtual machine stack is too small, can throw SOF problems when memory cannot be allocated.
2.2.2 VIRTUAL Machine Stack OOM Test
public class OOM {
JVM startup parameter: -xss1m */
public static void VMStackOOM(a) {
for(;;) {new Thread(()->{
while (1= =1){} }).start(); }}/ /!!!!!! Run carefully, most of the time will cause the OS to freeze!!
public static void main(String[] args){
// Call the method to test the VM stack OOMVMStackOOM(); }}Copy the code
In fact, the Java stack OOM is very difficult to observe, because the condition of stack OOM is: if the stack space cannot be allocated enough memory space, an OOM exception will be raised. However, this condition is almost impossible to achieve in HotSpot, because the amount of space required by the virtual stack is determined at compile time, and there is very little chance that the Java stack will dynamically expand during runtime, so we use another way to observe stack overflow in the above code. The VMStackOOM() method keeps creating new threads and keeping them active. Finally, when the JVM creates a thread, it allocates stack space for it, assuming that the machine has run out of memory, and OOM appears.
In the above example, the -xss parameter is used to specify the size of the virtual machine stack to be 1MB, but this approach is not very effective, please use caution! In most cases this will cause your machine/computer to run out of operating system resources and fall into suspended animation, which will run like this:
Exception in thread "main" java.lang.OutOfMemoryError:
unable to create new native thread
........
Copy the code
As you can see from the above results, an OOM exception is thrown when a thread is being created, but this is not really an overflow of stack memory, only a “barely” Java stack overflow in a sense. Because each Java thread, when created, requests resources from the OS and maps to a kernel thread, each Java thread occupies a certain amount of memory space, which occurs when the OS is unable to allocate memory for a newly created thread when physical memory runs out.
If you want to see a real Java stack overflow in HotSpot, there is actually another way:
In the previous discussion of the virtual stack, we mentioned that most of the space required by the virtual stack is determined at compile time, so it is almost impossible to satisfy stack overflow conditions in programs based on this rule. But things are not absolute, we know after analyzing the stack frame: Method into the participation at run time on locally stored in variables, and local variables in the stack frame, frame in the virtual machine stack, that if we were to write programs, define methods, the method is defined as the number of the participation of uncertain number, so the method corresponding to the stack frame size of the space needed compile time cannot be determined, As a result, the virtual stack can be dynamically expanded during runtime. The code is as follows:
JVM startup parameter: -xss256k */
public static void VMStackOOM(long. l) {}
Copy the code
We use -xss to specify the stack size to be 256KB and the size of an input parameter of type long to be 8bytes. 256KB =(1024*256)bytes. In theory, when you call VMStackOOM() and pass 10W long arguments into the method, you can definitely observe the condition of Java stack OOM.
But you ask me why I don’t post the execution result, because passing 10W parameters to a method is a big project, you can try it yourself
2.2.3 Cause and solution of VM Stack OOM
In most cases, there are only two reasons for OOM in the area of virtual machine stack. One is the problem caused by infinite recursion resulting in a large number of stack frames, and the other is the problem caused by infinite creation of new threads resulting in exhaustion of physical memory. In fact, these two are not the real meaning of the VIRTUAL machine stack OOM, the former is called SOF problem, the latter is caused by resource exhaustion.
- SOF problem:
- Cause: It is usually caused by infinite recursion.
- Solution: Optimize your code so that you can use recursion, but don’t produce infinite recursion.
Unable to create new native thread
Question:- Causes:
- ① The number of threads exceeds the maximum number of threads in the OPERATING system
ulimit
Restrictions. - ② The number of threads exceeds the threshold
kernel.pid_max
The number of kernel mappings specified in a process. - ③ The memory of the physical machine is used up and there is not enough memory to allocate new threads.
- ① The number of threads exceeds the maximum number of threads in the OPERATING system
- Solution:
- Upgrading Hardware Configurations
- use
-Xss
Reduce the size of the Java stack - Example Modify the default operating system parameters
- Causes:
2.3 Metadata Space and runtime Constant pool OOM
Metadata space mainly stores the class name, access modifiers, constant pool, field description, method description and other information. The basic idea of memory overflow of test metadata space is as follows: a large number of class bytecodes are generated during runtime, so that the memory of metadata space is exhausted, and OOM is thrown. Examples are as follows:
public class OOM {
// Test the object class for memory overflow
public static class OomObject{}
JVM startup parameters: -xx :PermSize=10M -XX:MaxPermSize=10M
public static void RuntimeConstantPoolOOM(a){
// Use List to keep references to the constant pool from being reclaimed by the Full GC
List<String> list = new ArrayList<>();
// 10MB PermSize is sufficient to generate OOM in the Integer range
int i = 0;
while (true) { list.add(String.valueOf(i++).intern()); }}/ method to test the metadata space OOM * * * * JVM startup parameters: - XX: MetaspaceSize = 10 m * - XX: MaxMetaspaceSize = 10 m * - XX: + HeapDumpOnOutOfMemoryError * * /
public static void MetaSpaceOOM(String[] args){
while (true) {
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(OomObject.class);
enhancer.setUseCache(false); enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o,args)); enhancer.create(); }}public static void main(String[] args){
// Call the test metadata space OOM methodMetaSpaceOOM(args); }}Copy the code
In the above example, we set the size of the metadata space to 10MB using the JVM parameter, and then use the CGLIB dynamic proxy of the enhancer object to produce a large number of bytecode-like files to fill the metadata space, and finally achieve OOM effect. The result is as follows:
java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid13784.hprof ...
Heap dump file created [4383328 bytes in 0.026 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
......
Copy the code
Can clearly see from the results of metadata space OOM logs: Java. Lang. OutOfMemoryError: Metaspace.
For the runtime constant pool OOM test, in JDK1.6, the string constant pool is in the runtime constant pool, so it is easier to test, just generate a large number of strings. The RuntimeConstantPoolOOM() method can also be used to run the string constant pool () method in a 1.6 environment. The RuntimeConstantPoolOOM() method can be used to run the string constant pool in a 1.6 environment. You can also observe a run-time constant pool memory overflow.
2.3.1 Reasons and solutions for OOM Metadata Space
The reasons for metadata space overflow are as follows:
- ① Too much information about loaded classes causes OOM
- ②JIT generated too much hot code, resulting in OOM
- ③ The runtime constant pool overflows, causing OOM
OOM for this area, because of the reason of is located in the local memory, so a general screening out due to the additional generated a lot of the proxy class that the reasons lead to OOM, other cases are general because of the memory allocated to support producing the runtime data, in this case the general distribution of large space through the corresponding parameters.
You can enable the -xx :+CMSClassUnloadingEnabled and -xx :+UseConcMarkSweepGC parameters to allow the JVM to unload classes. By default, the JVM does not unload classes. These dynamic proxy-generated classes have a short life cycle and may not be used for a long time after being loaded and used once, at which point the JVM can unload them automatically.
2.4. Direct MEMORY OOM
As mentioned earlier, the size of direct memory can be specified with the -xx :MaxDirectMemorySize argument, as shown in the following example:
public class OOM {
JVM startup parameters: -xmx10m -xx :MaxDirectMemorySize=10M */
public static void DirectMemoryOOM(a){
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = null;
try {
unsafe = (Unsafe) unsafeField.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
while (true) {
// Request 1MB of direct memory
unsafe.allocateMemory(1024*1024); }}public static void main(String[] args){
// Call the method to test the VM stack OOMDirectMemoryOOM(); }}Copy the code
In the Unsafe object above, the -xx :MaxDirectMemorySize/ -xmx method was used to specify the metadata size and maximum heap size of 10MB, and then the allocateMemory() method was used to retrieve the Unsafe object’s allocateMemory() method. The final result is as follows:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
.......
Copy the code
In the DirectMemoryOOM() method, the direct memory usage is kept in a loop, but is not released after the direct memory usage is applied, and on the 11th request, the allocated direct memory space is exhausted, thus throwing an OOM error.
2.4.1 Direct Memory OOM causes and solutions
Direct memory OOM has two main causes. One is that the allocated memory space is not properly released after the application and used up all the allocated memory space before FullGC arrives. The other is that the requested memory size exceeds the available memory size of the direct memory. In both cases, you can try to ensure that you recycle direct memory manually when you run out of it and don’t rely on the JVM’s GC mechanism to manage memory, or you can increase the size of direct memory to ensure that you have enough memory to use.
Memory Leak
Memory leak means that the memory allocated by the program is not released or cannot be released due to some reasons, resulting in the waste of system memory. In Java, is refers to the application of memory space is not properly released, is stored in the data after the use of the region is not recycled, while direct Pointers point to the area, but there are other references can be associated with the area, causing data have failed and reference chain remains, GC can’t recovery, Eventually, the memory in the subsequent program is occupied forever (unreachable), so the memory space is nipped, and finally the program runs slowly and runs out of memory.
For example: I run a POS game store with 100 locations, and I have the latest POS console for each location. Dear friend according to the location of the distribution in table, each brought a game to start playing the game, originally should be take to get the game after finishing off in his seat, so that I can according to your seat number, in turn, recovery of each friend’s game, but several malicious guy after finishing is not shut down, Results also shun away my game run, so that I can’t according to seat number to recycle the console, and so I would have left more than ninety units to the next friend to play, so on, each time several “steal” incident, resulting in my game store in the units all have no…
A typical example of a memory leak in Java is the use of ThreadLocal, as detailed in the ThreadLocal analysis section of Concurrent Programming. In addition, Java applications can leak memory due to a large number of static members, improperly closing connections, incorrect equals() and hashCode(), inner classes that refer to external classes, incorrectly overriding Finalize () methods, and constant strings.
Memory leaks can be roughly divided into four categories from the perspective of their occurrence modes:
- (1) Frequent memory leaks: In this case, the code that leaks memory will be executed multiple times, and each execution will cause a memory area to leak.
- (2) Accidental memory leaks: The code that leaks memory can only occur under certain circumstances or operations.
- ③ One-time memory leak: the code with memory leak will only be executed once in the process of program execution, but it is normal when executed twice.
- (4) Implicit memory leak: the program allocates memory continuously during execution, but does not release it until the end.
Either type of leak is relatively rare in Java, where the GC mechanism is well-established, so the chance of a memory leak is very, very small, especially in recent versions of Java, where the chance of a memory leak is near zero. However, the risk of memory leaks in earlier JDK versions was quite high, as method Area was not effectively recycled in Early Sun HotSpot, and Java programs often had this problem during execution.
When an OOM problem is thrown, a Memory image analysis tool (such as Eclipse Memory Analyzer) is used to analyze the heap dump snapshot. The focus is to determine whether the object in Memory is necessary and whether it is caused by Memory leak or overflow. If it is a memory leak, you can further look at the chain of references from the leaking object to GC Roots using tools such as Jrockit. You can then find out how the leaking object is associated with GC Roots and causes the garbage collector to fail to collect automatically.
Other memory overflow problems
In the OOM, the memory overflow problem in some common areas is briefly introduced. In the next part, several rare memory overflow problems are introduced.
4.1. Out of swap Space
Out of swap space indicates that all available virtual memory has been used up. Virtual memory consists of physical memory and swap space. This error is raised when the running time program overruns the requested virtual memory. There are two main reasons for this problem, one is insufficient address space, the other is the physical memory has been used up, the solution is generally only to improve the hardware configuration.
Kill process or Sacrifice child
“Kill Process or Sacrifice Child” is an error thrown by the Linux operating system. When the system is running Out of available Memory, the Out of Memory Killer component of the kernel will score all processes. An attempt is then made to kill some low-scoring processes and free up their memory space to ensure that there is enough memory to keep the OS running. In general, you don’t have to worry about this problem in Java programs, because scoring is based on activity, and Java programs typically run continuously after deployment.
4.3, Requested array size exceeds VM Limit
The JVM limits the maximum size of an array. This error indicates that the program is requesting an array that exceeds the maximum size limit. An error (Requested Array size exceeds VM Limit) can be found when an array is allocated, because its physical memory must be contiguous.
If you run into this kind of problem in your program, you’re going to have to split it up from the business, so if you have an array that’s this big and you can split it up into multiple queries, you can split it up into multiple different arrays.
Five, the summary
This is mainly for the JVM memory areas and each area runtime problems to conduct a comprehensive analysis, for out of memory and memory leak problem, appear in the online environment, the process of screening tend to be more complicated than we described a lot, but the ideas, the clear details after natural can screen out some of the problems. Of course, you should also learn to use various JVM tools such as Eclipse Memory Analyzer, ARMS, Arthas, and some of the tools that come with the JDK.