当 Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保(Handle Promotion)
在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的
如果不成立,则虚拟机会先检查是否允许担保失败。如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于或者不允许,那这时就要改为进行一次 Full GC
3.5.1 风险
新生代使用复制收集算法,但为了内存利用率,只使用其中一个 Survivor 空间来作为轮换备份,因此当出现大量对象在 Minor GC 后仍然存活的情况,且 Survivor 空间无法全部容纳,就需要老年代进行分配担保。当然前提是老年代本身还有容纳这些对象的剩余空间,但一共有多少对象会在这次回收中活下来在实际完成内存回收之前是无法明确知道的,所以只能取之前每一次回收晋升到老年代对象容量的平均大小作为经验值,与老年代的剩余空间进行比较,决定是否进行 Full GC 来让老年代腾出更多空间
4. 垃圾回收算法
Serial 收集器依然是客户端模式下默认的新生代收集器,简单而高效(与其他收集器的单线程相比),对于内存资源受限的环境,它是所有收集器里额外内存消耗最小的;对于单核处理器或处理器核心数较少的环境来说,Serial 收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率
5.2 ParNew 收集器
实质上是 Serial 收集器的多线程并行版本,在实现上这两种收集器也共用了相当多的代码
是不少运行在服务端模式下虚拟机的首选收集器,尤其是 JDK1.7 之前的遗留系统,其中有一个与功能、性能无关但其实很重要的原因是:除了 Serial 收集器外,目前只有它能与 CMS 收集器配合工作
5.3 Parallel Scavenge 收集器
基于标记-复制算法实现的收集器,是一款新生代收集器,也是能够并行收集的多线程收集器
它的关注点与其他收集器不同,CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 运行垃圾收集时间)
停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验;而高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务
5.4 Serial Old 收集器
Serial Old 是 Serial 收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。这个收集器的主要意义也是供客户端模式下的虚拟机使用。如果在服务端模式下,它也可能有两种用途:一种是在 JDK1.5 以及之前的版本中与 Parallel Scavenge 收集器搭配使用,另外一种就是作为 CMS 收集器发生失败时的后备预案
5.5 Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现
直到 Parallel Old 收集器出现后,吞吐量优先收集器终于有了比较名副其实的搭配组合,在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器这个组合
Parallel Scavenge 与 Parallel Old 也是 JDK1.8 默认收集器
5.6 CMS 收集器
CMS(Concurrent Mark Sweep)收集器基于标记-清除算法,是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java 应用集中在互联网网站或者基于浏览器的 B/S 系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS 收集器就非常符合这类应用的需求
基于标记-清除算法实现的收集器,收集结束时会有大量空间碎片产生
5.7 Garbage First (G1)收集器
G1 是一款主要面向服务端应用的垃圾收集器。在 G1 收集器出现之前的所有其他收集器,包括 CMS 在内,垃圾收集的目标范围要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),再要么就是整个堆(Full GC)。而 G1 可以面向堆内存任何部分来组成回收集(Collection Set,CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是 G1 收集器的 Mixed GC 模式
G1 开创了基于 Region 的堆内存布局,不在按照分代区域划分,而是把堆划分为多个大小相等的独立区域(Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。收集器能够对扮演不同角色的 Region 采用不同的策略去处理
Region 中还有一类特殊的 Humongous 区域,专门用来存储大对象。G1 认为只要大小超过了一个 Region 容量一半的对象即可判定为大对象。而对于那些超过了整个 Region 容量的超级大对象,将会被存放在多个连续的 Humongous Region 之中,G1 会把 Humongous Region 作为老年代的一部分来进行看待
G1 收集器将 Region 作为单次回收的最小单元,即每次收集到的内存空间都是 Region 大小的整数倍,这样可以有计划地避免在整个堆中进行全区域的垃圾收集。更具体的处理思路是让 G1 收集器去跟踪各个 Region 里面的垃圾堆积的价值大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(默认值是 200 毫秒),优先处理回收价值收益最大的那些 Region,这也就是 Garbage First 名字的由来
5.7.1 工作流程
筛选回收(Live Data Counting and Evacuation):负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行
完成的
5.8 垃圾收集器的选择
串行:Serial、Serial Old
并发:ParNew、Parallel Scavenge、Parallel Old、CMS、G1
新生代:Serial、ParNew、Parallel Scavenge
老年代:Serial Old、Parallel Old、CMS
G1 保留了分代的概念,但并不局限于收集某个分代
标记-清除:CMS
标记-复制:Serial、ParNew、Parallel Scavenge
标记-整理:Serial Old、Parallel Old
G1 从整体来看是基于标记-整理算法,但从局部(两个 Region 之间)上看又是基于标记-复制算法