并发垃圾回收
文章目录
cms 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于并发“标记清理”实现,在标记清理过程中不会导致用户线程无法定位引用对象。仅作用于老年代收集。它的步骤如下:
-
初始标记(CMS initial mark):独占CPU,stop-the-world, 仅标记GCroots能直接关联的对象,速度比较快;
-
并发标记(CMS concurrent mark):可以和用户线程并发执行,通过GCRoots Tracing 标记所有可达对象;
-
重新标记(CMS remark):独占CPU,stop-the-world, 对并发标记阶段用户线程运行产生的垃圾对象进行标记修正,以及更新逃逸对象;
-
并发清理(CMS concurrent sweep):可以和用户线程并发执行,清理在重复标记中被标记为可回收的对象。
- 支持并发收集.
- 低停顿,因为CMS可以控制将耗时的两个stop-the-world操作保持与用户线程恰当的时机并发执行,并且能保证在短时间执行完成,这样就达到了近似并发的目的.
g1收集器
G1收集器的内存结构完全区别去CMS,弱化了CMS原有的分代模型(分代可以是不连续的空间),将堆内存划分成一个个Region(1MB~32MB, 默认2048个分区),这么做的目的是在进行收集时不必在全堆范围内进行。它主要特点在于达到可控的停顿时间,用户可以指定收集操作在多长时间内完成,即G1提供了接近实时的收集特性。它的步骤如下:
- 初始标记(Initial Marking):标记一下GC Roots能直接关联到的对象,伴随着一次普通的Young GC发生,并修改NTAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,此阶段是stop-the-world操作。
- 根区间扫描,标记所有幸存者区间的对象引用,扫描 Survivor到老年代的引用,该阶段必须在下一次Young GC 发生前结束。
- 并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行,该阶段可以被Young GC中断。
- 最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,此阶段是stop-the-world操作,使用snapshot-at-the-beginning (SATB) 算法。
- 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,回收没有存活对象的Region并加入可用Region队列。这个阶段也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。
卡表(Card Table)
有个场景,老年代的对象可能引用新生代的对象,由于新生代的垃圾收集通常很频繁,那标记存活对象的时候,需要扫描从老年代到新生代的所有引用对象。因为该对象拥有对新生代对象的引用,那么这个引用也会被称为GC Roots。那不是每次YGC时又得做全堆扫描?显然不是,对于HotSpot JVM,使用了卡标记(Card Marking)技术来解决老年代到新生代的引用问题。具体是,使用卡表(Card Table)和写屏障(Write Barrier)来进行标记并加快对GC Roots的扫描。卡表的设计师将堆内存平均分成2的N次方大小(默认512字节)个卡,并且维护一个卡表,用来储存每个卡的标识位。当对一个对象引用进行写操作时(对象引用改变),写屏障逻辑将会标记对象所在的卡页为脏页。在YGC只需要扫描卡表中的脏卡,将脏中的对象加入到YGC的GC Roots里面。当完成所有脏卡扫描时候,虚拟机会将卡表的脏卡标志位清空。
在高并发环境下,每次对引用的更新,无论是否更新了老年代对新生代对象的引用,都会进行一次写屏障操作,频繁的写屏障很容易发生虚共享(false sharing),从而带来性能开销。举 个例子:假设CPU缓存行大小为64字节,由于一个卡表项占1个字节,这意味着,64个卡表项将共享同一个缓存行。HotSpot每个卡页为512字节,那么一个缓存行将对应64个卡页一共 64*512=32KB。如果不同线程对对象引用的更新操作,恰好位于同一个32KB区域内,这将导致同时更新卡表的同一个缓存行,从而造成缓存行的写回、无效化或者同步操作,间接影响程序 性能。
文章作者 LYR
上次更新 2021-08-17