判断对象是否存活
引用计数
每个对象都有一个引用计数器,当被引用的时候,计数器加1,引用失效的时候计数器减1。计数器为0时表示对象可以被回收。
优点:计数简单,效率高
缺点:容易出现两个对象相互引用导致一直没法被回收
可达性分析
GC Root
可作为GC Roots 的对象:
- 虚拟机栈中(栈帧中的本地变量表)引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 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))
三色标记法
将每个对象进行标记,分为黑白灰三种颜色 白色:还未被垃圾回收器标记的对象 灰色:自身已经被标记,但其拥有的成员变量还未被标记 黑色:自身已经被标记,且对象本身所有的成员变量也已经被标记 最后白色的对象就是可被回收的。
多标:标记过程中,已经标记为活动的对象,变成了应该被回收的对象。(问题不大,等待下次GC回收即可),入下图C在标记过程中,失去了和B的引用,变成了浮动垃圾。
漏标:如下图,标记过程中,B被标记,还没开始标记B的成员变量,这个时候B和C之间引用断了,A和C建立了引用,因为A已经是黑色的,所以不会再去标记C,导致C被错误认为是可被回收的。解决方法:当A新增引用时,将其变成灰色状态。
三种基本垃圾回收算法
标记-清除,标记-压缩,复制算法,这三种是基本算法,其它算法基本上是这几种算法的组合或改进。
-
标记-清除:从GC Root出发,将所有活动对象打上标记,然后回收未标记的对象。 缺点:会出现大量的内存碎片,导致内存空间充足但是大对象内存可能会分配失败,无法分到连续的内存空间。
-
标记-压缩(标记-整理):从GC Root出发,将所有活动对象打上标记,然后将标记对象移动到另外一端。 优点:不会出现碎片化问题 缺点:压缩/整理阶段相对标记清除需要额外时间
-
复制算法:将内存分为两块,每次只使用一块,当一块放不下了,就将还存活的复制到另外一块,然后清除第一块的空间。两块交替存放。
优点:效率高、不会产生内存碎片
缺点:每次只能使用一半内存空间,空间利用率低
Java中的分代收集算法
年轻代:采用复制算法,由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变为灰色,重新再遍历一次。如下图
这种解决方案会导致下面的问题,在并发标记阶段,当多个垃圾回收线程执行的时候,会产生漏标,所以在重新标记阶段需要从GC Root开始遍历标记。
- 垃圾回收线程1:B标记完,开始标记C
- 工作线程1:把D指向了B
- 垃圾回收线程2:把B变成灰
- 垃圾回收线程1:标记完C,把A变成了黑色,这时候D被漏标
G1
- 初始标记 STW
- 并发标记
- 重新标记 STW
- 筛选回收 STW,多条垃圾回收线程并发
如上图,内存被分为一个个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的时候针对这些对象进行标记。
都看到这里了,微信搜索 [ 序员说 ] 关注公众号,持续获取最新文章