图文并茂的Java GC总结

487 阅读6分钟

判断对象是否存活

引用计数

每个对象都有一个引用计数器,当被引用的时候,计数器加1,引用失效的时候计数器减1。计数器为0时表示对象可以被回收。

优点:计数简单,效率高

缺点:容易出现两个对象相互引用导致一直没法被回收

可达性分析

GC Root

可作为GC Roots 的对象:

  1. 虚拟机栈中(栈帧中的本地变量表)引用的对象
  2. 方法区中的类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中 JNI(Native 方法) 引用的对象

从一个根对象出发,往下遍历,遍历完之后没遍历过的就是可被回收的对象。简单点说就是根对象是树的顶点,然后遍历一棵树,最后不在这棵树中的就是可被回收的。如下图F就是可被回收的。

graph TB

A((GC Root))
B((B))
C((C))
D((D))
E((E))
A-->B
A-->C
A-->D
B-->E
F((F))
三色标记法

将每个对象进行标记,分为黑白灰三种颜色 白色:还未被垃圾回收器标记的对象 灰色:自身已经被标记,但其拥有的成员变量还未被标记 黑色:自身已经被标记,且对象本身所有的成员变量也已经被标记 最后白色的对象就是可被回收的。

三色标记法.png

多标:标记过程中,已经标记为活动的对象,变成了应该被回收的对象。(问题不大,等待下次GC回收即可),入下图C在标记过程中,失去了和B的引用,变成了浮动垃圾。 三色标记-多标.png

漏标:如下图,标记过程中,B被标记,还没开始标记B的成员变量,这个时候B和C之间引用断了,A和C建立了引用,因为A已经是黑色的,所以不会再去标记C,导致C被错误认为是可被回收的。解决方法:当A新增引用时,将其变成灰色状态。

三种基本垃圾回收算法

标记-清除,标记-压缩,复制算法,这三种是基本算法,其它算法基本上是这几种算法的组合或改进。

  1. 标记-清除:从GC Root出发,将所有活动对象打上标记,然后回收未标记的对象。 缺点:会出现大量的内存碎片,导致内存空间充足但是大对象内存可能会分配失败,无法分到连续的内存空间。 垃圾回收-标记-清除.png

  2. 标记-压缩(标记-整理):从GC Root出发,将所有活动对象打上标记,然后将标记对象移动到另外一端。 优点不会出现碎片化问题 缺点压缩/整理阶段相对标记清除需要额外时间

垃圾回收-标记-整理.png

  1. 复制算法:将内存分为两块,每次只使用一块,当一块放不下了,就将还存活的复制到另外一块,然后清除第一块的空间。两块交替存放。

    优点效率高、不会产生内存碎片

    缺点每次只能使用一半内存空间,空间利用率低

垃圾回收-复制.png

Java中的分代收集算法

垃圾回收-Java.png

年轻代:采用复制算法,由Eden区和两个Survivor区组成,比例8:1:1,两个Survivor区也叫from区和to区。

新生成的对象,先向Eden区申请空间,Eden区空间不足,触发Minor GC,将Eden区和Survivor0区存活对象复制到Survivor1区,Survivor1空间空间不足以放入时,直接放到老年代。,然后清空Eden区和Survivor0区,将Survivor0和Survivor1交换,(一开始的时候Survivor区都是空的,所以第一次是Eden区复制到Survivor区,后面就是Survivor0和Survivor1循环交换,每次GC对象年龄加1,年龄达到一定值(默认15)时会进入老年代)

老年代:采用标记-清除标记-整理算法

各种垃圾回收器

STW:Stop The World,指垃圾回收的时候所有其它工作线程暂停。当没有STW的时候,就有可能出现多标、漏标的情况。


Serial

作用于年轻代,单线程,采用复制算法,需要STW

Serial Old

作用于老年代,跟Serial搭配使用


Parallel Scavenge

作用于年轻代,多线程,主要关注点时吞吐量,采用复制算法,需要STW 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间

Parallel Old

作用于老年代,跟Parallel Scavenge搭配使用


ParNew

作用于年轻代,Serial的多线程版本,采用复制算法,需要STW

CMS(Concurrent Mark Sweep)

并发标记清理,这里的并发是指工作线程和垃圾回收线程同时在运行,CMS是运行在老年代的。跟ParNew配合使用。 主要步骤: 1.初始标记:STW,主要标记GC Root,因为GC Root不多,所以这个阶段耗时不多。 2.并发标记:工作线程和垃圾回收线程同时运行。没有STW,所以会出现漏标或错标。 3.重新标记:STW,主要是修正并发标记过程中被错标的对象。 4.并发清理:工作线程和垃圾回收线程同时运行。

Incremental Update:解决漏标问题,当一个对象A新增一个引用的时候,将A变为灰色,重新再遍历一次。如下图 三色标记-漏标-CMS-问题.png

这种解决方案会导致下面的问题,在并发标记阶段,当多个垃圾回收线程执行的时候,会产生漏标,所以在重新标记阶段需要从GC Root开始遍历标记。

  1. 垃圾回收线程1:B标记完,开始标记C
  2. 工作线程1:把D指向了B
  3. 垃圾回收线程2:把B变成灰
  4. 垃圾回收线程1:标记完C,把A变成了黑色,这时候D被漏标

三色标记-漏标.png


G1

  1. 初始标记 STW
  2. 并发标记
  3. 重新标记 STW
  4. 筛选回收 STW,多条垃圾回收线程并发

垃圾回收-G1.png

如上图,内存被分为一个个region,灰色部分为未分配的内存空间,对象大小超过region大小的一半的,就会被分配到老年代,每个region只有一个大对象。 年轻代包含Eden区和Survivor区,和CMS不同的是年轻代的空间是会动态变化的,当Eden区空间不足时,会从空闲内存空间分配新的Eden Region。

Remembered Sets(RSet)

每个region中的一块区域,记录其它引用当前region的region对象

Collection Set(CSet)

本次GC需要清理的region集合

Yonug GC(年轻代 gc)

采用复制算法 当Eden区空间不足以存放新对象时触发,yong gc执行完,存活对象会被拷贝survivor或者老年代。

Mixed GC(混合GC)

对年轻代进行GC,选择部分回收收益高的老年代进行回收,老年代采用复制算法。

Snapshot-At-The-Beginning(SATB)

解决CMS中会出现的漏标问题

在删除引用的时候,不改变父对象的状态,而是从GC Root建立连接到被删除的对象,下次remark的时候针对这些对象进行标记。


都看到这里了,微信搜索 [ 序员说 ] 关注公众号,持续获取最新文章