JVM垃圾回收器发展与对比
一,GC回收算法:
1,引用计数法
对象中使用计数器。此算法无法解决互相引用的场景。
2,可达性分析算法
从gcRoots集合出发,查找引用链,如没有链路链接的对象,则初次判断为垃圾。
gcRoots集合一般包括两类:全局的引用(如常量)、执行上下文(栈帧中本地变量表)。
具体细节:
2.1根节点枚举阶段:
不会把所有执行上下文和全局的引用位置全部查出来,而是使用准确式垃圾收集,是使用一组称为OopMap的数据结构,一旦类加载动作完成的时候,会把引用直接记录在map集合中。收集器在扫描时可以直接得知map中信息了,并不需要真正一个不漏地从方法区等GC Roots开始查找。
2.2第一次判断:
gcRoots的链路中没有链接的对象,第一次标记为垃圾。
2.3筛选:
件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。
2.4第二次标记:
如果为有必要执行:将这些第一次标记的垃圾对象放置在一个名为F-Queue的队列中,由一条由虚拟机自动建立的、低调度优先级的线程执行它们的finalize()方法。
收集器将对F-Queue中的对象进行第二次小规模的标记,如果此对象重新与引用链上的任何一个对象建立关联,则会被移出“即将回收”的集合。
2.5执行:
finalize();
经典垃圾回收器比较
衡量垃圾收集器的三项最重要的指标是:内存占用(Footprint)、吞吐量(Throughput)和延迟(Latency),三者共同构成了一个“不可能三角”。三者总体的表现会随技术进步而越来越好,但是 要在这三个方面同时具有卓越表现的“完美”收集器是极其困难甚至是不可能的,一款优秀的收集器通 常最多可以同时达成其中的两项。
Hotspot垃圾回收器
1.Serial收集器
特点,一条收集线程去完成垃圾收集工作,但是必须暂停其他所有工作线程,直到它收集结束。可能会使用户线程停止响应。
优点:所有收集器里额外内存消耗最少(没有线程交互的开销,专心做垃圾收集自然可以 获得最高的单线程收集效率)
缺点:暂停其他所有线程
1.1.Serial Old收集器
Serial收集器的老年代版本,同样是一个单线程收集器,标记-整理算法。
2.ParNew收集器
Serial收集器多线程版本,除了Serial收集器外,目前只有它能与CMS 收集器配合工作
优点:处理器核心数量的增加,ParNew对于垃圾收集时 系统资源更高效
3.Parallel Scavenge收集器(吞吐量优先收集器)
Parallel Scavenge收集器的目标是达到一个可控制的吞吐 量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。
两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间 的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
3.1Parallel Old收集器
Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。在注重 吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合。
4.CMS(划时代意义)
第一款真正意义上支持并发的垃圾收集器,它首次实现了让垃圾收集线程与用户线程(基本上)同时工作。关注点是以获取最短回收停顿时间为目标的收集器。
过程分为四个步骤,包括:
1)初始标记(CMS initial mark)
只是标记一下GC Roots能直接关联到的对象,速度很快。
2)并发标记(CM S concurrent mark)
从GC Roots的直接关联对象开始遍历整个对 象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
3)重新标记(CM S remark)
为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的 标记记录。
4)并发清除(CM S concurrent sweep)
清理删除掉标记阶段判断的已经死亡的 对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
优点:并发收集、低停顿。
缺点:1,占用了一部分线程(或者说处理器的计 算能力)而导致应用程序变慢,降低总吞吐量
2,CMS收集器无法处理“浮动垃圾”(FloatingGarbage),有可能出现“Con-current Mode Failure”失败进而导致另一次完全“Stop The World”的Full GC的产生。
3,空间碎片产生。无法找 到足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC。
5.G1(里程碑)
面向全堆的收集器,不 再需要其他新生代收集器的配合工作。目标是在延迟可控的情况下获得尽可能高的吞吐量。
G1收集器出现之前的所有 其他收集器,包括CMS在内,垃圾收集的目标范围要么是整个新生代(Minor GC),要么就是整个老 年代(M ajor GC),再要么就是整个Java堆(Full GC)。而G1跳出了这个樊笼,它可以面向堆内存任 何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而 是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
Java堆划分为多个大小相等的独立区域(Region),是一系列区 域(不需要连续)的动态集合,每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的 Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的 旧对象都能获取很好的收集效果。
G1收集器去跟踪各个Region里面的垃 圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一 个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:M axGCPauseM illis指定,默 认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。 这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获 取尽可能高的收集效率。
过程:
初始标记(Initial M arking)
仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAM S 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。
并发标记(Concurrent Marking)
从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理SAT B记录下的在并发时有引用变动的对象。
最终标记(Final M arking)
对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SAT B记录。
筛选回收(Live Data Counting and Evacuation)
负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。
低延迟垃圾收集器
Shenandoah
Shenandoah的目标是实现一种能在任何堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的垃圾收集器,该目标意味着相比CMS和G1,Shenandoah不仅要进行并发的垃圾标记,还要并发地进行对象清理后的整理动作。
Shenandoah反而更像是G1的继承者,它们两者有着相似的堆内存布局,在初始标记、并发标记等许多阶段的处理思路上都高度一致。
G1的回收阶段是可以多线程并行的,但却不能与用户线程并发。Shenandoah通过转发指针来解决这个问题。
Shenandoah三个最重要的并发阶段(并发标记、并发回收、并发引用更新)。
未完待续。。