Java memory region and memory overflow exception
Runtime data area
Program counter
- It is used to record the address of the next instruction executed from memory. It is a small piece of memory private to the thread, and it is also the only area that will not report OOM exceptions
Java virtual machine stack
- Java Virtual Machine Stack is thread private, and its life cycle is the same as that of thread. Virtual machine stack describes the thread memory model of Java method execution: when each method is executed, the Java virtual machine will synchronously create a Stack Frame to store local variable table, operand stack, dynamic connection, method exit and other information. Each method is called until it is executed, which corresponds to the process of a Stack Frame from entering the stack to leaving the stack in the virtual machine stack
- If the stack depth requested by the thread is greater than the depth allowed by the virtual machine, a StackOverflowError exception will be thrown
- If the stack capacity of the Java virtual machine can be dynamically expanded, an OutOfMemoryError exception will be thrown if enough memory cannot be applied for when the stack is expanded
Native Method Stack
- Similar to the Java virtual machine stack, but different from the service object, the local method stack serves the local method used by the virtual machine, and the Java virtual machine stack executes the Java method (bytecode) service for the virtual machine
Java heap
- For Java applications, Java Heap is the largest piece of memory managed by the virtual machine. The Java Heap is a memory area shared by all threads and is created when the virtual machine starts. The only purpose of this memory area is to store object instances. Almost all object instances in the Java World allocate memory here
- When the heap memory does not have enough space to allocate memory for the object instance and the heap memory cannot be expanded, an OOM exception will be thrown
Method area
- Similar to the Java heap, the method area is also shared by various threads. It is used to store type information, constants, static variables, code cache compiled by the real-time compiler and other data that have been loaded by the virtual machine
- The alias "non heap" is usually used to distinguish it from Java heap
- When the method area does not have enough space to meet the memory allocation requirements, an OOM exception will also be thrown
Runtime Constant Pool
- The runtime constant pool is a part of the method area and is used to store various literal and symbolic references generated during compilation
- Due to the memory limitation of the method area, an OOM exception will be thrown when the constant pool can no longer apply for memory
Direct memory
- Direct memory is not part of the runtime data area, but it is limited by the total memory, and OOM exceptions may occur
HotSpot virtual machine object exploration
Object creation
After the class loading check passes, the virtual machine will allocate memory for the new object, and there are two main memory allocation methods:
-
Pointer collision
-
free list
Object's memory layout
In the HotSpot virtual machine, the storage layout of objects in heap memory can be divided into three parts: object Header, Instance Data and Padding
-
Object header
- Store the object's own runtime data (Mark Word), such as HashCode, GC generation age, lock status flag, lock held by thread, biased thread ID, biased timestamp, etc
- Type pointer (pointer of an object to its type metadata)
-
Instance data
- The effective information that the object really stores, that is, the contents of various types of fields in the code
-
Align fill
- Since the automatic memory management system of HotSpot virtual machine requires that the starting address of the object must be an integer multiple of 8 bytes, that is, the size of any object is an integer multiple of 8 bytes, if the instance data part is not aligned, it needs to be aligned and filled to act as a placeholder
Object access location
The Java program will operate the specific objects on the heap through the reference data on the stack. The specific access method is implemented by the virtual machine.
There are two main access methods:
-
handle
-
Direct pointer
Actual OOM exception
Different JDK and garbage collection collectors may produce different results. The following practical operations take JDK8 and ParallelGC garbage collectors as examples to run the code
# View default garbage collector VM parameters -XX:+PrintCommandLineFlags -version
Java heap overflow
As long as you keep creating object instances and avoid garbage collector collection, you can generate OOM exceptions after reaching the maximum heap capacity limit
public class Hello { /** * -Xms: Minimum heap memory 20M -Xmx: maximum heap memory 20M. The two settings are the same to avoid automatic expansion * VM Parameters: - Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError */ public static void main(String[] args) { List<Hello> hellos = new ArrayList<>(); while (true) { hellos.add(new Hello()); } } }
Java virtual machine stack and native method stack overflow
The Java virtual machine specification explicitly allows the Java virtual machine to choose whether to support the dynamic expansion of the stack, while the HotSpot virtual machine does not support the expansion. Therefore, unless the OutOfMemoryError exception occurs due to the inability to obtain enough memory when creating the thread to apply for memory, the memory overflow will not be caused by the expansion when the thread is running, The StackOverflowError exception is only caused because the stack capacity cannot accommodate new stack frames
- Use the - Xss parameter to reduce stack capacity
public class Hello { /** * VM Parameter: - Xss128k */ private int stackLength = 1; public void stackLeak() { stackLength++; // Call the method recursively and keep putting it on the stack stackLeak(); } public static void main(String[] args) throws Throwable { Hello oom = new Hello(); try { // Call method, stack oom.stackLeak(); } catch (Throwable e) { System.out.println("stack length:" + oom.stackLength); throw e; } } }
- A large number of local variables are defined. Increase the length of the local variable table in this method frame (that is, adjust the stack frame size)
public class Hello { private static int stackLength = 0; public static void test() { // There are many local variables and the stack frame increases long unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10, unused11, unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20, unused21, unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30, unused31, unused32, unused33, unused34, unused35, unused36, unused37, unused38, unused39, unused40, unused41, unused42, unused43, unused44, unused45, unused46, unused47, unused48, unused49, unused50, unused51, unused52, unused53, unused54, unused55, unused56, unused57, unused58, unused59, unused60, unused61, unused62, unused63, unused64, unused65, unused66, unused67, unused68, unused69, unused70, unused71, unused72, unused73, unused74, unused75, unused76, unused77, unused78, unused79, unused80, unused81, unused82, unused83, unused84, unused85, unused86, unused87, unused88, unused89, unused90, unused91, unused92, unused93, unused94, unused95, unused96, unused97, unused98, unused99, unused100; stackLength++; // Recursive call, continuous stack test(); unused1 = unused2 = unused3 = unused4 = unused5 = unused6 = unused7 = unused8 = unused9 = unused10 = unused11 = unused12 = unused13 = unused14 = unused15 = unused16 = unused17 = unused18 = unused19 = unused20 = unused21 = unused22 = unused23 = unused24 = unused25 = unused26 = unused27 = unused28 = unused29 = unused30 = unused31 = unused32 = unused33 = unused34 = unused35 = unused36 = unused37 = unused38 = unused39 = unused40 = unused41 = unused42 = unused43 = unused44 = unused45 = unused46 = unused47 = unused48 = unused49 = unused50 = unused51 = unused52 = unused53 = unused54 = unused55 = unused56 = unused57 = unused58 = unused59 = unused60 = unused61 = unused62 = unused63 = unused64 = unused65 = unused66 = unused67 = unused68 = unused69 = unused70 = unused71 = unused72 = unused73 = unused74 = unused75 = unused76 = unused77 = unused78 = unused79 = unused80 = unused81 = unused82 = unused83 = unused84 = unused85 = unused86 = unused87 = unused88 = unused89 = unused90 = unused91 = unused92 = unused93 = unused94 = unused95 = unused96 = unused97 = unused98 = unused99 = unused100 = 0; } public static void main(String[] args) { try { test(); } catch (Error e) { System.out.println("stack length:" + stackLength); throw e; } } }
Method area and runtime constant pool overflow
- Method area capacity control
public class Hello { /** * JDK8 Front VM parameters: - XX:PermSize=6M -XX:MaxPermSize=6M * JDK8VM Parameters: - XX:MetaspaceSize=6M -XX:MaxMetaspaceSize=6M */ public static void main(String[] args) { // Use Set to keep constant pool reference and avoid Full GC reclaiming constant pool Set<String> set = new HashSet<>(); // Within the short range, it is enough to generate OOM for a 6M size PermSize (permanent generation, pre JDK8, and JDK8 and later versions have been replaced by meta space) short i = 0; // Throw OOM exception before JDK8 // Under JDK8, normally, it will enter an endless loop without throwing any exceptions while (true) { // String.intern() enters string constant pool set.add(String.valueOf(i++).intern()); } } }
The above code will not throw any exceptions in the JDK8 environment because the string constant pool has been moved to the Java heap, and the size of the control method area has no impact on the Java heap
- String. Introduction to the intern() method: if the string constant pool already contains a string equal to this string object, the string object of this string in the constant pool will be returned; Otherwise, copy and add the characters contained in this string object to the constant pool and return a reference to this string object
/** * JDK6: false false * JDK8: true false */ public static void main(String[] args) { String str1 = new StringBuilder("computer").append("Software").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2); }
-
JDK6 because new StringBuilder() allocates Java heap memory, while string Intern () will copy the string encountered for the first time to the string constant pool (method area), so they are all false
-
JDK8 because the String constant pool is moved to the Java heap, after new StringBuilder() allocates the Java heap memory, the String constant pool also records the instance reference encountered for the first time, so String intern() and new StringBuilder() are the same (true); Because the Java String is in sun misc. When the version class is loaded, it has entered the constant pool, then the intern () method returns the String object of the current constant pool, and new StringBuilder () recreates one in the heap, which is naturally different (false)
-
The main responsibility of the method area is to store type related information, such as class name, access modifier, constant pool, field description, method description, etc. Therefore, a large number of classes will be generated during runtime to fill the method area, which can also cause overflow of the method area
/* * Method area overflow with CGLib * VM Parameters: - XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M */ public class Hello { public static void main(String[] args) { while (true) { // Create CgLib enhancement object Enhancer enhancer = new Enhancer(); // Sets the class to be represented enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); // Designated interceptor enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }); // Create proxy object enhancer.create(); } } static class OOMObject { } }
Native direct memory overflow
The capacity of Direct Memory can be specified by the - XX: MaxDirectMemorySize parameter. If it is not specified, it is consistent with the maximum value of Java heap (specified by - Xmx) by default
// Allocate native memory using unsafe public class Hello { // VM parameters: - Xmx20M -XX:MaxDirectMemorySize=10M private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { // Real request to allocate memory unsafe.allocateMemory(_1MB); } } }
reference material
In depth understanding of Java virtual machine (Third Edition), Chapter 2: Java memory region and memory overflow exception