java垃圾回收转载整理(未完待续)

参考链接:
文章1:https://www.cubrid.org/blog/understanding-java-garbage-collection
文章2:https://blog.csdn.net/u012152619/article/details/46981643
文章3:https://tech.meituan.com/g1.html

本文主要是根据英文文档进行整理。如有不对,欢迎指正。

分代回收不算是一种算法,而是一种策略,在年轻代和年老代可以采用不同的回收算法,这时候用的算法就是网上一些说的复制回收,标记整理等等。每次进行垃圾回收的时候,整个应用程序会有短暂的停止,整个状态叫做STW(stop-the-world),垃圾回收的调优就是减少这个状态的时间。

新生代,老年代是在虚拟内存中的一种人为划分。

Young generation(新生代):绝大部分的对象在这里创建,而且很快就变成不可达对象,可以被回收,新生代的垃圾回收称为minor GC。新生代由一个Eden区,两个Survivor 区(from 和 to)组成。from 和to没有明确的前后优先级关系,两者轮流使用。新生代内存容量默认比例分别为8:1:1,同时使用Eden区以及一个Survivor区。

Old generation(老年代):存储没有变成不可达的对象,或者从新生代移动过来的对象。老年代的存储空间远大于新生代。老年代的垃圾回收称为major GC 或者full GC,频率低于minor GC。只有当年老区存储空间不足时才会进行GC。大对象会直接分配在老年代中。

永久代:用来存储类信息,基本不参与到垃圾回收的进程中。JDK 8去除了永久代,引入了元空间Metaspace

card table(卡表):当老年代中的对象引用了新生代中的对象,会在卡表中存储相关信息,新生代垃圾回收的时候,会扫描卡表,来决定是否回收对象。

新生代垃圾回收过程(根据文章1):
step1:当新生代Eden区空间不够使用时,开始垃圾回收Eden区
step2:经过垃圾回收之后,Eden区中存活的对象会被移动到存在部分对象的Survivor区(from)。
step3:如果from区满了,from区对象会被移动到Survivor to区。并将from区置为空状态。
step4:Survivor区中经过几轮minor GC的对象会被移动到年老区。(根据文章2,此处默认为15轮)

文章1中有这么一句话:如果两个Survivor区同时为空,或者同时存在数据,那么表示系统已经出现了异常。

这里有几个疑问
1、from区满了,移动到to区,to区不是还是满的吗,什么时候做垃圾回收呢
2、如果不满15轮,又没有可以销毁的数据怎么办呢

结合文章2,有以下猜测,但是没有找到官方的文章和源码解读。
step1:当新生代Eden区空间不够使用时,开始垃圾回收Eden区
step2:经过垃圾回收之后,Eden区中存活的对象会被移动到存在部分对象的Survivor区(from)。
step3:如果from区满了,from区对象会被移动到Survivor to区。对to区进行GC,如果还是不足以存放内容,那么会将经历过多轮的对象移动到年老区(此处为猜测,且多轮也不知道是什么标准,或者是将一半的对象移动到年老区?),并将from区置为空状态。下次Eden区GC之后的对象会移动到to区。
step4:Survivor区中经过几轮minor GC的对象会被移动到年老区。(根据文章2,此处默认为15轮)

不同的垃圾回收器采用不同的垃圾回收算法,这里翻译了文章1中的几种垃圾回收器,翻译的不好,轻喷。

Serial GC (-XX:+UseSerialGC),串行垃圾回收器,使用的算法叫做mark-sweep-compact(标记-回收-压缩)。
它分两个阶段执行,在第一个阶段,首先扫描所有活跃的对象,并标记所有活跃的对象,第二个阶段首先清除未标记的对象,然后将活跃的的对象复制到堆得底部。使得存储空间是连续的,解决了内存碎片的问题。串行GC适合内存小,CPU内核小的系统。

Parallel GC (-XX:+UseParallelGC),并行垃圾回收器,和串行GC算法一致,区别在于串行是单线程,并行是多线程执行,因此效率会高一点。适合内存和cpu足够大的系统。

Parallel Old GC(-XX:+UseParallelOldGC),老年代并行垃圾回收器,只适用于老年代的垃圾回收。此处使用的算法是mark-sweep-compact。这个算法暂时没有找到解释,文章1中的说法如下,并不是特别清楚,后续查到相关资料再补充

The summary step identifies the surviving objects separately for the areas that the GC have previously performed, and thus different from the sweep step of the mark-sweep-compact algorithm.

CMS GC(-XX:+UseConcMarkSweepGC)。从文章1中盗图一张,对照图片比较好理解。
这里写图片描述
图片中蓝色部分是正常的程序 运行,橙色部分是STW阶段或者GC阶段。
initial mark阶段比较简单,从最接近类加载器的对象开始标记。concurrent mark阶段在应用程序阶段并行执行,从已标记的对象的引用开始标记存活对象。remark阶段,标记在concurrent mark阶段新增加的对象和失去引用的对象。然后在应用程序过程中并行清理。此算法的STW时间很短暂,适合对于应用响应时间比较看重的应用程序。
但是此算法也有如下缺点:此算法比其他算法要耗费更多的内存和cpu。且默认不会执行整理操作(compaction)。如果需要执行整理操作,需要仔细考虑多久执行一次整理操作,每次整理操作可以执行多久。因为整理操作将耗费比其他算法更长的STW时间。

G1 GC:
G1被提出来用来替代CMS,标记阶段也是采用并发标记的方式。文章3讲述的比较清楚,把其中的一些概念,GC过程摘抄如下:

Region
G1的各代存储地址是不连续的,每一代都使用了n个不连续的大小相同的Region,每个Region占有一块连续的虚拟内存地址,如图所示,图片来自文章3:
这里写图片描述
在上图中,我们注意到还有一些Region标明了H,它代表Humongous,这表示这些Region存储的是巨大对象(humongous object,H-obj),即大小大于等于region一半的对象。H-obj有如下几个特征:

H-obj直接分配到了old gen,防止了反复拷贝移动。
H-obj在global concurrent marking阶段的cleanup 和 full GC阶段回收。
在分配H-obj之前先检查是否超过 initiating heap occupancy percent和the marking threshold, 如果超过的话,就启动global concurrent marking,为的是提早回收,防止 evacuation failures 和 full GC。

三色标记法:
G1采用三色标记法来标记回收对象,三色标记法可以参考以下文章:
http://idiotsky.me/2017/08/16/gc-three-color/
http://www.cnblogs.com/qqmomery/p/6661574.html
三色:

白:对象没有被标记到,标记阶段结束后,会被当做垃圾回收掉。
灰:对象被标记了,但是它的field还没有被标记或标记完(引用的对象没有标记完)。
黑:对象被标记了,且它的所有field也被标记完了(引用对象标记完了)。

write barrier:写屏障
来自wiki的对这个术语的解释:”A write barrier in a garbage collector is a fragment of code emitted by the compiler immediately before every store operation to ensure that (e.g.) generational invariants are maintained.” 即是说,在每一处内存写操作的前面,编译器会生成的一小段代码段,来确保不要打破一些约束条件。 增量和分代,都需要维护一个write barrier。
三色标记法中的约束条件为:黑色对象绝对不能引用白色对象,参考链接:https://www.zhihu.com/question/62000722

为什么不能让黑色引用白色?因为黑色对象是活跃对象,它引用的对象是也应该属于活跃的,不应该被清理。但是,由于在三色标记算法中,黑色对象已经处理完毕,它不会被重复扫描。那么,这个对象引用的白色对象将没有机会被着色,最终会被误当作垃圾清理。 STW中,一个对象,只有它引用的对象全标记后才会标记为黑色。所以黑色对象要么引用的黑色对象,要么引用的灰色对象。不会出现黑色引用白色对象。 对于垃圾回收和用户代码并行的场景,用户代码可能会修改已经标记为黑色的对象,让它引用白色对象。看一个例子来说明这个问题:

stack -> A.ref -> B
A是从栈对象直接可达,将它标记为灰色。此时B是白色对象。假设这个时候用户代码执行:

localRef = A.ref
A.ref = NULL
localRef是栈上面的一个黑色对象,前一行赋值语句使得它引用到B对象。后一行A.ref被置为空之后,A将不再引用到B。A是灰色但是不再引用到B了,B不会着色。localRef是黑色,处理完毕的对象,引用了B但是不会被再次处理。于是B将永远不再有机会被标记,它会被误当作垃圾清理掉!

以上所说的情况就是文章3中提到的白色漏标的情况:

由于并发阶段的存在,Mutator和Garbage Collector线程同时对对象进行修改,就会出现白对象漏标的情况,这种情况发生的前提是:
Mutator赋予一个黑对象该白对象的引用。
Mutator删除了所有从灰对象到该白对象的直接或者间接引用。

SATB 是一种基于快照思想的write barrier实现,用了防止白色漏标的情况。

未完待续(大家可以直接看两篇文章)

猜你喜欢

转载自blog.csdn.net/zzp448561636/article/details/80338262