Unlike the C language, the allocation and recycling of Java memory (heap memory) is automatically completed by the JVM garbage collector. This feature is very popular and can help programmers write code better. This article takes the HotSpot virtual machine as an example to talk about Java GC.
In the article about JVM memory, we already know that the Java heap is a memory area shared by all threads, and all object instances and arrays All memory allocation is done on the heap. In order to perform efficient garbage collection, the virtual machine divides the heap memory into three areas: Young Generation, Old Generation and Permanent Generation.
The new generation consists of Eden and Survivor Space (S0, S1), and the size is specified by the -Xmn parameter. The memory size ratio between Eden and Survivor Space defaults to 8:1, which can be specified through the -XX:SurvivorRatio parameter. For example, when the new generation is 10M, Eden is allocated 8M, and S0 and S1 are each allocated 1M.
Eden: Greek, meaning the Garden of Eden. In the Bible, the Garden of Eden means paradise. According to the records in the Old Testament Genesis, God Jehovah created the first earth in his own image. A man, Adam, created a woman, Eve, from one of Adam's ribs and placed them in the Garden of Eden.
In most cases, objects are allocated in Eden. When Eden does not have enough space, a Minor GC will be triggered. The virtual machine provides the -XX:+PrintGCDetails parameter to tell the virtual machine to print when garbage collection occurs. Memory reclamation log.
Survivor: It means survivor and is the buffer area between the new generation and the old generation.
When GC (Minor GC) occurs in the new generation, the surviving objects will be moved to the S0 memory area and the Eden area will be cleared. When the Minor GC occurs again, the surviving objects in Eden and S0 will be moved to S1 memory area.
Surviving objects will repeatedly move between S0 and S1. When the object moves from Eden to Survivor or between Survivors, the GC age of the object automatically accumulates. When the GC age exceeds the default threshold of 15, it will To move the object to the old generation, you can set the GC age threshold through the parameter -XX:MaxTenuringThreshold.
The space size of the old generation is the difference between the two parameters -Xmx and -Xmn, which is used to store objects that are still alive after several Minor GCs. . When there is insufficient space in the old generation, Major GC/Full GC will be triggered, which is generally more than 10 times slower than Minor GC.
In the HotSpot implementation before JDK8, the metadata of the class such as method data, method information (bytecode, stack and variable size), runtime The constant pool, determined symbol references and virtual method tables are saved in the permanent generation. The default size of the permanent generation is 64M for 32-bit and 85M for 64-bit. It can be set through the parameter -XX:MaxPermSize. Once the class is If the metadata exceeds the permanent generation size, an OOM exception will be thrown.
The virtual machine team removed the permanent generation from the Java heap in HotSpot of JDK8, and saved the metadata of the class directly in the local memory area (off-heap memory), which is called metaspace.
What are the benefits of doing this?
Experienced students will find that the tuning process of the permanent generation is very difficult. The size of the permanent generation is difficult to determine because it involves too many factors, such as the total number of classes, the size of the constant pool, the number of methods, etc. , and the data in the permanent generation may move with each Full GC.
In JDK8, the metadata of the class is stored in local memory. The maximum allocable space of the metaspace is the system’s available memory space, which can avoid the memory overflow problem of the permanent generation, but the memory consumption needs to be monitored. , once a memory leak occurs, it will occupy a large amount of local memory.
ps: In HotSpot before JDK7, strings in the string constant pool were stored in the permanent generation, which may cause a series of performance problems and memory overflow errors. In JDK8, only string references are stored in the string constant pool.
Before the GC action occurs, it is necessary to determine which objects in the heap memory are alive. There are generally two methods: reference counting and accessible. Expressive analysis method.
1. Reference counting method
Add a reference counter to the object. Whenever an object references it, the counter increases by 1. When the object is used up, , the counter is decremented by 1, and an object with a counter value of 0 indicates that it cannot be used anymore.
The reference counting method is simple to implement and efficient in determination, but it cannot solve the problem of mutual references between objects.
public class GCtest { private Object instance = null; private static final int _10M = 10 * 1 << 20; // 一个对象占10M,方便在GC日志中看出是否被回收 private byte[] bigSize = new byte[_10M]; public static void main(String[] args) { GCtest objA = new GCtest(); GCtest objB = new GCtest(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc(); } }
By adding the -XX:+PrintGC parameter, the running result is:
[GC (System.gc()) [PSYoungGen: 26982K->1194K(75776K)] 26982K->1202K(249344K), 0.0010103 secs]
从GC日志中可以看出objA和objB虽然相互引用,但是它们所占的内存还是被垃圾收集器回收了。
2、可达性分析法
通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,以下对象可作为GC Roots:
本地变量表中引用的对象
方法区中静态变量引用的对象
方法区中常量引用的对象
Native方法引用的对象
当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。
在可达性分析法中,判定一个对象objA是否可回收,至少要经历两次标记过程:
1、如果对象objA到 GC Roots没有引用链,则进行第一次标记。
2、如果对象objA重写了finalize()方法,且还未执行过,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize()方法。finalize()方法是对象逃脱死亡的最后机会,GC会对队列中的对象进行第二次标记,如果objA在finalize()方法中与引用链上的任何一个对象建立联系,那么在第二次标记时,objA会被移出“即将回收”集合。
看看具体实现
public class FinalizerTest { public static FinalizerTest object; public void isAlive() { System.out.println("I'm alive"); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("method finalize is running"); object = this; } public static void main(String[] args) throws Exception { object = new FinalizerTest(); // 第一次执行,finalize方法会自救 object = null; System.gc(); Thread.sleep(500); if (object != null) { object.isAlive(); } else { System.out.println("I'm dead"); } // 第二次执行,finalize方法已经执行过 object = null; System.gc(); Thread.sleep(500); if (object != null) { object.isAlive(); } else { System.out.println("I'm dead"); } } }
执行结果:
method finalize is running I'm alive I'm dead
从执行结果可以看出:
第一次发生GC时,finalize方法的确执行了,并且在被回收之前成功逃脱;
第二次发生GC时,由于finalize方法只会被JVM调用一次,object被回收。
当然了,在实际项目中应该尽量避免使用finalize方法。
Java GC 的那些事(1)
Java GC的那些事(2)
以上就是Java GC 的那些事(1)的内容,更多相关内容请关注PHP中文网(www.php.cn)!