wangjie_fourth

may the force be with you

0%

GC算法

在新的篇章里,梳理一下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
2
-XX:+UseSerialGC
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps

并行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/