文章分享-20周-垃圾收集器与内存分配策略(下)

本文章内容基于《深入了解Java虚拟机》第二版,对里面知识点进行总结,JDK主要是1.6和1.7。

以下内容围绕HotSpot虚拟机的各种垃圾收集起实现。
目前没有一个最好的收集器,都是针对不同区域的特性实现最好垃圾收集方法


HotSpot虚拟机的垃圾收集器

Serial收集器

单线程的收集器(JDK1.3之前新生代收集器唯一选择)
注意:单线程不是表示使用单个CPU或一个收集线程,而是进行垃圾收集,必须暂停其他所有工作线程

Serial / Serial Old收集器运行示意图

后续JDK不断更新,目前只能不断缩短暂停时间,无法完全消除(排除RTSJ的收集器)
应用范围:目前应用虚拟机运行在Client模式下的新生代收集器。
优点:简单而高效(与其他收集器的单线程对比)
原因:
1、单CPU环境,Serial收集器没有线程交互的开销
2、用户的桌面应用场景,虚拟机管的内容一般不大,暂停时间可以控制到接受范围。

ParNew收集器

ParNew为Serial的多线程版本,其他与Serial完全一样


PerNew / Serial Old收集器运行示意图

应用范围:运行于Server模式的虚拟机首先新生代收集器
原因:
1、只有Serial与PerNew能与CMS收集配合工作(CMS收集器是优秀并发收集器,实现垃圾线程和用户线程基本上同时工作;CMS作为老年代的收集器)

Parallel Scavenge收集器

该收集器是新生代收集器,使用复制算法,且是多线程
特点:其他收集器都为缩短暂停用户线程的时间,该收集器目标则是达到一个可控的吞吐量。
吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间)

对比:
1、暂停时间越短:适合需要与用户交互的程序,提交响应速度。
2、吞吐量高:高效率使用CPU,尽快完成程序的运算任务,适合后台运算不需要太多交互的任务。

注意:GC暂停时间缩短一般通过牺牲吞吐量和空间来实现。

Serial Old收集器

Serial收集器的老年代版本,单线程收集器,使用标记-整理算法,Client模式下使用。
用途:
1、与Parallel Scavenge收集器搭配使用
2、作为CMS收集器的后备预案。

Parallel Old收集器

是Parallel Scavenge收集器的老年代版本,多线程、标记-整理算法。
用途:
Parallel Scavenge收集器无法与CMS收集器配合,Serial Old单线程在服务端性能不足。推出Parallel Old收集器配合Parallel Scavenge收集器在服务端提性能。

Parallel Scavenge / Parallel Old 收集器运行示意图

CMS收集器

是一种一获取最短回收暂停时间为目标的收集器,目前广泛应用互联网站或B/S系统的服务端。基于标记-清除算法
分为四个阶段:
1、初始标记:标记GC Roots能直接关联的对象;暂停用户线程
2、并发标记:进行GC Roots Tracing过程
3、重新标记:修正并发标记期间因用户程序运行导致标记变动;暂停用户线程
4、并发清除:多线程清除无用对象。


Concurrent Mark Sweep 收集器运行示意图

缺点:
1、CMS收集器对CPU资源非常敏感,占用一部分CPU资源,导致应用程序变慢,总吞吐量降低。(CPU数量越少,CMS性能变低)
2、CMS收集器无法处理浮动垃圾。并发清除过程中,用户线程还会生产新的垃圾,需要等到下次GC才能清除。
而且运行过程还要给用户线程预留内存空间,当出现预留的空间无法满足程序需求,则要临时启动Serial Old收集器逻辑,
3、采用标记-清除算法会出现很多碎片,不足是会进行 full GC操作

G1收集器

面对服务端应用的垃圾收集器。替换CMS收集器
特点:
1、并行与并发:充分利用多CPU、多核环境的优势
2、分代收集:G1不需要与其他收集器配合,独立管理整个GC堆
3、空间整合:整体为标记整理算法,局部为复制算法(两个region之间)
4、可预测的停顿:建立可预测的停顿时间模型,能够让使用者明确指定的一个长度为M毫秒的时间片段内。垃圾收集回收不超过N毫秒。

内存分配:
G1收集起将整个Java堆分为多个大小相等的独立区域(新生代和老生代不在物理隔离,是一部分region的集合)

建立可预测的停顿时间模型:
G1跟踪各个region的垃圾堆积的价值大小(收集获取空间以及回收所需时间),后台维护一个优先列表,优先回收价值最大的region。

问题:单独region内的对象会被其他region对象引用,难道还是要整个Java堆扫描?
答:使用Remembered Set来避免全表扫描,每个region都有一个对应的Remembered Set。为了登记引用对象被其他region对象引用的信息。
进行内存回收时,会引入Remembered Set保证不对全堆扫描。

如果不包含维护Remembered Set,步骤为
1、初始标记:标记GC Roots能直接关联的对象;暂停用户线程
2、并发标记:进行GC Roots Tracing过程
3、最终标记:修正并发标记期间因用户程序运行导致标记变动;暂停用户线程
该阶段会维护Remembered Set
4、筛选回收:多线程清除无用对象。

G1收集器运行示意图

内存分配与回收策略总结

对象内存分配的大方向就是堆上分配。
独享主要分配在新生代的Eden区。如果启动本地线程分配缓冲,将按照线程优先在TLAB上分配。少数情况也可能直接分配在老年代。

对象优先分配在Eden分配

大多数情况之下,对象在新生代Eden区中分配,当Eden区没有足够空间,虚拟机会进行一个Minor GC。

Minor GC与Full GC区别
新生代(Minor GC):新生代的垃圾回收,非常频繁,一般回收速度较快。
老年代(Major GC/Full GC):老年代的垃圾回收,出现Major GC之前一般至少出现一次Minor GC,Minor GC速度一般比Minor GC慢10倍以上。

老年代

大对象直接进入老年代:
大对象是指需要大量连续的内存空间对象(长字符串以及数组)。
长期存活的对象将进入老年代:
每个对象都会定义一个年龄(Age)计算器,每次Minor GC之后还存在,则年龄+1。随着年龄增加到一定程度(默认为15)就晋升到老年代。

动态对象年龄判定:
虚拟机不是一直要求年龄必须达到指定年龄才能进入老年代。如果Survivor空间中相同年龄所有对象大小总和大于survivor空间的一半,年龄大于或则等于该年龄的对象就可以直接进入老年代。

空间分配担保:
Minor GC之前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果条件成立,那么Minor GC可以确保是安全。
如果条件不成立,会检查是否允许担保失败:
如果允许:继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。
如果大于:尝试进行一次Minor GC(该GC存在风险)
如果小于或则不允许冒险:则进行一次Full GC

何为冒险?
新生代使用复制算法,其中一个survivor空间作为轮换备份,如果出现大量对象在Minor GC之后存在,则需要老年代进行分配担保,把survivor无法容纳的对象直接进入老年代。冒险在于老年代剩余空间是否足够。

你可能感兴趣的:(文章分享-20周-垃圾收集器与内存分配策略(下))