在新的篇章里,梳理一下JVM中关于GC的知识点,这里仅包含一些基础的知识点,有一些常用内容(比如G1)会在后面再写。 大致的内容包含:
(1)基础知识
- 对象的生命周期
- 垃圾对象判定
- 可达性分析
- 引用计数法
- GC Root
- 四种引用类型
(2)GC算法
- 分代假设
(3)垃圾回收器
- 串行垃圾回收器
- 并行垃圾回收器
- 并发垃圾回收器
- G1
(3)GC日志分析
基础知识
对象的生命周期
在java中,你只需要创建对象,对象的回收是由GC来完成的。比如:当GC工作后,判定这个对象是垃圾对象,会将这个对象放入死亡队列中,然后GC会遍历这个死亡队列,调用对象的finalize方法。在执行完这个方法后,该对象还是垃圾对象的话,才会被正式被回收。如下图:
垃圾对象判定
可达性分析
这是绝大数的垃圾回收算法的垃圾判定方法。在整个JVM中,在垃圾回收之前,会先判断哪些对象一定不是垃圾对象,并称之为GC Root。然后凡是直接、间接引用GC Root的对象,我们都认为是非垃圾对象;那其他对象就是垃圾对象了。
引用计数法
在整个JVM中,如果一个对象被引用了,就将计数加1;如果这个对象被其他对象放弃引用了,就将计数减1。当一个对象计数为0时,那么这个对象就是垃圾对象了。这种方式的缺点就是有些垃圾对象可能会成环,那这些对象就永远都不会是垃圾对象,导致内存泄漏。
GC Root
GC Root是指那些一定不是垃圾的对象。具体有:
- 所有的Thread对象
- 栈帧中的局部变量表
- 方法区中的静态成员
- JNI持有的引用
- Monitor:用于同步的锁对象
- 其他类型的GC Root:比如说在进行年轻代GC时,老年代对象也是GC Root
四种引用类型
这四种引用类型主要是体现在对GC垃圾对象判定上。
1、强引用:StrongReference
强引用就是普通引用的对象。只要还有一个强引用指向这个对象,那么这个对象就不是垃圾对象。
2、软引用:SoftReference
软引用是一种稍弱于强引用的一种引用。当一个对象只有被软引用时,且内存不足时,这个对象就会有回收。
3、弱引用:WeakReference
弱引用是比软引用还弱的一种引用。当一个对象只有被弱引用时,只要发生GC,这个对象就会被回收。
(1)相关应用
- ThreadLocal中的ThreadLocalMap的Entry就是一个WeakReference
- guava的cache
- WeakHashMap
坑点:在做缓存时,有时候因为不知道什么时机删除或者是内存敏感的缓存时,会通过设置缓存为弱引用,来减少缓存大小。但是这里有个坑,如果缓存都没了,就可能会出现缓存雪崩,导致服务不可用。
4、虚引用:PhantomReference
虚引用,不会决定对象的生命周期,提供了一种确保对象被finalize以后,去做某些事情的机制。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要进行垃圾回收,然后我们就可以在引用的对象的内存回收之前采取必要的行动。
GC算法
分代假设
分代假设是认为jvm中绝大多数的对象都是朝生夕死的,只有少数一些对象才会需要一直存在。
所以JVM会把对象分为年轻代(Young)、老年代(Tenured)、永久代(PermGen),比如下图:
这里的永久代其实是指jdk1.8之前,方法区是占用堆空间时。在jdk1.8之后,永久代其实就没什么实际意义了。
年轻代
年轻代的内存区域分为三个部分:Eden、Survivor1、Survivor2。
年轻代的GC算法使用的是Mark and Copy。当Eden区满了的时候,会触发Young GC。Eden和Survivor1(假设此时S1有对象,S2无对象)区域会触发垃圾回收,并将剩余对象的拷贝放入S2区域中。并且此时剩余的对象年纪加1,在年纪超过15后,年轻代对象会进入到老年代区域。如下图:
老年代
老年代主要使用的是Mark and Compact算法。其中主要的就是在垃圾回收之后的压缩过程。
垃圾回收器
串行垃圾回收器
年轻代:Mark-Copy,使用的是Serial垃圾回收器
老年代:Mark-Sweep-Compact,使用的是Serial Old垃圾回收器
1 | -XX:+UseSerialGC |
并行GC:Parallel GC
年轻代:Mark-Copy,使用的是ParNew、ParallelScavenge垃圾回收器
老年代:Mark-Sweep-Compact,使用的是Parallel Old垃圾回收器
使用多个线程并发标记清除
并发GC:CMS:Concurrent Mark Sweep
年轻代:Mark-Copy,使用ParNew垃圾回收器
老年代:Mark-Sweep,使用CMS垃圾回收器
1、GC过程
CMS是设计用于进一步降低延迟时间,在老年代垃圾回收时,不再进行Compact。大致过程如下:
- 初始标记
- 只标记GC Roots和其直接引用的对象
- 并行标记
- 根据上面用的标记,再次对所有对象进行标记
- 此时用户线程正常工作
- 重新标记(并发)
- 因为在上述过程中,用户线程还在工作,有些之前认为是垃圾对象,可能又被重新引用了。所以在这里并发的进行标记
- 此时用户线程停止工作
- 并行清理
- 此时用户线程正常工作
缺点:无法进行Compact,可能会造成内存碎片化。
youkit官方文档:https://www.yourkit.com/docs/java-profiler/2022.9/help/gc_roots.jsp
oracle官方GC调优文档:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/