JVM internal structure diagram
Java virtual machines are mainly divided into five areas: method area, heap, Java stack, PC register, and local method stack. Let’s take a look at some important issues about JVM structure.
1. Which areas are shared? Which ones are private?
The Java stack, local method stack, and program counter are established and destroyed as the user thread starts and ends.
Each thread has these independent areas. The method area and the heap are shared by all threads in the entire JVM process.
2. What is saved in the method area? Will it be recycled?
The method area is not only saved method information and code. At the same time, in a sub-region called the runtime constant pool, various symbolic references in the constant table in the Class file, as well as translated direct references. This information is accessed through a Class object in the heap as an interface.
Although the type information is stored in the method area, it will also be recycled, but the recycling conditions are relatively strict:
(1) All instances of this class have been recycled
(2) The ClassLoader loading this class has been recycled
(3) The Class object of this class is not referenced anywhere (including Class.forName reflection access)
3. Is the content of the constant pool in the method area unchanged?
The runtime constant pool in the method area saves the data in the static constant pool in the Class file. In addition to storing various literal and symbolic references generated at compile time, it also contains translated direct references. But this does not mean that the constant pool will not change during runtime. For example, when running, you can call the String's intern method and put new string constants into the pool.
package com.cdai.jvm; public class RuntimeConstantPool { public static void main(String[] args) { String s1 = new String("hello"); String s2 = new String("hello"); System.out.println("Before intern, s1 == s2: " + (s1 == s2)); s1 = s1.intern(); s2 = s2.intern(); System.out.println("After intern, s1 == s2: " + (s1 == s2)); } }
4. Are all object instances allocated on the heap?
With the gradual maturity of escape analysis technology, on-stack allocation and scalar replacement optimization technologies have made "all objects allocated on the heap" less absolute.
The so-called escape means that when an object's pointer is referenced by multiple methods or threads, we say that this pointer escapes.
Generally speaking, Java objects are allocated in the heap, and only the object's pointers are saved in the stack. Assuming that a local variable does not escape during the execution of the method (exposed to the outside of the method), it will be allocated directly on the stack and then continue to be executed in the call stack. After the execution of the method, the stack space is recycled and the local variables are also recycled. This reduces the allocation of a large number of temporary objects in the heap and improves the efficiency of GC recycling.
In addition, escape analysis will also omit locks on local variables that have not escaped, and omit locks owned on this variable.
When enabling escape analysis method, add JVM startup parameters: -XX: +DoEscapeAnalysis?EscapeAnalysisTest.
5. How many ways are there to access objects on the heap?
(1) Direct access to pointer
The reference on the stack saves a pointer to an object on the heap, and you can locate the object in one go, which has a faster access speed.
However, when objects are moved in the heap (each objects are often moved during garbage collection), the value of the pointer variable on the stack also needs to be changed. Currently, JVM HotSpot adopts this method.
(2) Indirect access to handle
The reference on the stack points to a handle in the handle pool, and the object is accessed through the value in this handle. Therefore, the handle is like a secondary pointer, which requires two positioning to access the object, which is slower than the direct pointer positioning, but when the object moves in the heap, there is no need to change the value referenced on the stack.
How to overflow JVM
After understanding the role of the five memory areas of Java virtual machines, let’s continue to learn under what circumstances these areas will overflow.
1. Virtual machine parameter configuration
-Xms: The initial heap size, default is 1/64 of physical memory (<1GB); default (MinHeapFreeRatio parameter can be adjusted) When the free heap memory is less than 40%, the JVM will increase the heap until the maximum limit of -Xmx.
-Xmx: Maximum heap size, default (MaxHeapFreeRatio parameter can be adjusted) When the free heap memory is greater than 70%, the JVM will reduce the heap until the minimum limit of -Xms.
-Xss: Stack size for each thread. After JDK5.0, the stack size of each thread is 1M, and in the past, the stack size of each thread is 256K. It should be adjusted appropriately according to the required memory size of the application's thread. In the same physical memory, reducing this value can generate more threads. However, the operating system still has a limit on the number of threads in a process and cannot be generated infinitely, with experience values ranging from about 3000 to 5000. Generally, small applications, if the stack is not very deep, 128k should be enough. For large applications, 256k is recommended. This option has a great impact on performance and requires strict testing.
-XX:PermSize: Set the permanent generation (perm gen) initial value. The default value is 1/64 of physical memory.
-XX:MaxPermSize: Set the maximum value of the persistent generation. 1/4 of physical memory.
2. Method area overflow
Because the method area holds the relevant information of the class, when we load too many classes, the method area will overflow. Here we try to overflow the method area through two methods: JDK dynamic proxy and CGLIB proxy.
2.1 JDK dynamic proxy
package com.cdai.jvm.overflow; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MethodAreaOverflow { static interface OOMIinterface { } static class OOMObject implements OOMIinterface { } static class OOMObject2 implements OOMIinterface { } public static void main(String[] args) { final OOMObject object = new OOMObject(); while (true) { OOMIinterface proxy = (OOMIinterface) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), OOMObject.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Interceptor1 is working"); return method.invoke(object, args); } } ); System.out.println(proxy.getClass()); System.out.println("Proxy1: " + proxy); OOMIinterface proxy2 = (OOMIinterface) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), OOMObject.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Interceptor2 is working"); return method.invoke(object, args); } } ); System.out.println(proxy2.getClass()); System.out.println("Proxy2: " + proxy2); } } } Although we keep calling the Proxy.newInstance() method to create the proxy class, the JVM does not have a memory overflow.
Each call generates a different instance of the proxy class, but the Class object of the proxy class has not changed. Is it Proxy
Does the class have a cache for the Class object of the proxy class? The specific reasons will be analyzed in detail in the subsequent "JDK Dynamic Proxy and CGLIB".
2.2 CGLIB Agent
CGLIB will also cache the Class object of the proxy class, but we can configure it to not cache the Class object.
In this way, the purpose of overflowing the method area can be achieved by repeatedly creating proxy classes.
package com.cdai.jvm.overflow; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class MethodAreaOverflow2 { static class OOMObject { } public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return method.invoke(obj, args); } }); OOMObject proxy = (OOMObject) enhancer.create(); System.out.println(proxy.getClass()); } } }
3. Heap overflow
Heap overflow is relatively simple. You can overflow the heap by creating a large array object.
package com.cdai.jvm.overflow; public class HeapOverflow { private static final int MB = 1024 * 1024; @SuppressWarnings("unused") public static void main(String[] args) { byte[] bigMemory = new byte[1024 * MB]; } }
4. Stack Overflow
Stack overflow is also common. Sometimes when the recursive call we write does not have the correct termination condition, the method will continue to recurse, the depth of the stack will continue to increase, and eventually the stack overflow will occur.
package com.cdai.jvm.overflow; public class StackOverflow { private static int stackDepth = 1; public static void stackOverflow() { stackDepth++; stackOverflow(); } public static void main(String[] args) { try { stackOverflow(); } catch (Exception e) { System.err.println("Stack depth: " + stackDepth); e.printStackTrace(); } } }