# 对象已死吗?

# 引用计数算法

Java 不使用该算法作为管理内存算法,不能解决循环引用问题。但实现起来简单,效率也高。

# 可达性分析算法

从一系列称为 “GC Roots” 的对象作为始点,从这些节点往下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连,则该对象不可用。

在 Java 中可作为 GC Roots 的节点为:

  1. 虚拟机栈(栈帧中的本地变量表中)引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中 JNI (即 Native 对象) 引用的对象。

# 再谈引用

将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference),4 种引用强度依次逐渐减弱。

# 生存还是死亡?

至少经过两次标记过程;如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链。

  1. 第一次标记,并进行筛选:对象没有覆盖 finalize 方法,或者已经被虚拟机调用,则认为 “没有必要执行” 回收。
  2. 如果判定为必要执行 finalize 方法,则该对象会放置在一个 F-Queue 的队列之中。
  3. 稍后虚拟机自动建立的、低优先级的 Finalizer 线程去执行。
  4. 第二次对 F-Queue 中的对象进行第二次小规模的标记,如果对象在 finalize 中成功拯救自己 —— 重新与引用链建立关联。则从 F-Queue 中移除该对象。否则基本上就会被回收了。
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;
    public void isAlive() {
        System.out.println("yes, i am still alive :)");
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        // 下面代码自救,注释下面一行代码,该对象就会被回收
        FinalizeEscapeGC.SAVE_HOOK = this;
    }
    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
    }
}

可以忘记该 finalize 方法存在。

# 回收方法区

永久代主要回收:废弃常量和无用的类。

废弃常量,如果没有引用,在回收的时候,如果有必要回收,则会回收该常量,其它的类(接口)、方法、字段的符号也类似。

无用的类则比较严格:

  1. 该类所有实例都已被回收,Java 堆中不存在该类的任何实例。
  2. 加载该类的 ClassLoader 已经被回收。
  3. 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

是否对类进行回收,HotSpot 虚拟机提供了 -Xnoclassgc 参数控制。

-verbose:class -XX:+TraceClassLoading -XX:+TraceClassUnloading
查看类加载和卸载信息。

-verbose:class-XX:+TraceClassLoading 可以在 Product 版的虚拟机中使用。

-XX:+TraceClassUnloading 参数需要 FastDebug 版的虚拟机支持。

# 垃圾收集算法

# 标记 - 清除算法

首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

缺点:

  1. 标记和清除两个过程的效率都不高。
  2. 标记清除之后,产生大量不连续的内存空间。

mark_sweep

# 复制算法

  1. 将内存划分为大小相等的两块,每次只使用其中的一块。
  2. 当这一块内存用完了,就将还存活的对象复制到另外一块上,然后清理整块。

优点:
实现简单,效率高,不会产生碎片。

缺点:
内存缩小为一半。

copying

IBM 研究表面:新生代中的对象 98% 是 “朝生夕死” 的,所以并不需要按 1:1 分配内存空间。

而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间。

回收时,将 Eden 和 Survivor 中还存活的对象一次性地将对象复制到另外一块 Survivor 空间上,最后清理 Eden 和刚才用过的 Survivor 空间。

HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例为 8:1,所以每次新生代中可用的内存空间占总新生代容量的 90%(80%+10%),只有 10% 的内存会被 “浪费”。

但没办法保证每次回收都只有不多于 10% 的对象存活。当 Survivor 空间不够时,需依赖其它内存(老年代)进行分配担保(Handle Promotion)。

# 标记 - 整理算法

根据老年代的特点,提出的一种算法。让存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

mark_compact

# 分代收集算法

分代收集,一般是把 Java 堆分为新生代和老年代。

新生代中,每次垃圾收集时都发现有大批对象死去,只有少量对象存活,那就选用复制算法。

老年代中对象存活率高,没有额外空间对它进行分配担保,就必须使用 “标记 - 清理” 或 “标记 - 整理” 算法来回收。

# HotSpot 的算法实现

# 枚举根节点

问题?

  1. 可达性分析中,现在很多应用仅仅方法区就有数百兆,如果逐个检查引用,那么必然消耗很多时间。
  2. 可达性分析对执行时间的敏感还体现在 GC 停顿上;分析期间,必须停顿所有 Java 执行线程,确保一致性,防止期间对象引用还在变化。

处理:

主流的 Java 虚拟机,使用的都是准确式 GC。在 HotSpot 的实现中,使用一组 OopMap 的数据结构来达到不必检查所有执行上下文和全局引用位置的目的。

类加载完成的时候,HotSpot 就把对象内偏移量上对应的类型数据计算出来。在 JIT 编译中,也会在特定位置记录下栈和寄存器中哪些位置是引用。

这样,GC 在扫描时,可以直接得知这些信息。

# 安全点

OopMap 结构解决 HotSpot 可以快速且准确地完成 GC Roots 的枚举。

新问题?

引用关系变化,OopMap 内容变化的指令非常多,如果为每一个指令都生成对应的 OopMap,那将需要大量的额外空间,这样 GC 的空间成本将会变得很高。

解决:

HotSpot 在特定位置记录这些信息,这些位置称为安全点(Safepoint),只有到达安全点时才能暂停。

所以 Safepoint 的选定不能太少,也不能太频繁。

是否具有让程序长时间执行的特征:
指令序列复用特征,例如:方法调用,循环跳转,异常跳转等,具有这些功能的指令才会产生 Safepoint。

Safepoint 考虑的问题?

如何在 GC 发生时,让所有线程都 “跑” 到最近的安全点上再停顿下来。

抢先式中断(Preemptive Suspension)和主动式中断(Voluntary Suspension)

抢先式中断:GC 发生时,先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它跑到安全点上。现在几乎没有虚拟机采用这种方式。

主动式中断:简单的设置一个标志,各个线程执行时主动去轮询这个标志,如果发现中断标志为真,则自己中断挂起,轮询标志的地方与安全点是重合的。另外再加上创建对象需要分配内存的地方。

# 安全区域

当线程处于 Sleep 状态或者 Blocked 状态时,这时候线程无法响应 JVM 的中断请求,无法 “走” 到安全的地方去中断挂起。这种情况就需要安全区域(Safe Region)来解决。

安全区域是指在一段代码片段之中,引用关系不会发生变化。在这区域任意地方开始 GC 都是安全的,也可以把 Safe Region 看做是被扩展的 Safepoint。

线程执行到 Safe Region 中的代码时,首先标识自己已经进入 Safe Region,JVM 发起 GC,就不用管这些已标记的线程。

线程在 Safe Region 等待,直到 GC 已经完成根节点枚举,收到安全离开的 Safe Region 信号为止。

# 垃圾收集器

JDK 1.7 Update 14 之后的 HotSpot 虚拟机包含的垃圾收集器。

连线的则可以相互搭配使用。划线的区域分新生代和老年代。

collectors_of_HotSpot

# Serial 收集器

最基本、发展历史最悠久的单线程收集器。工作时,必须暂停其它的工作线程,直到它收集结束。

Colector_of_Serial_and_SerialOld

# ParNew 收集器

是 Serial 收集器的多线程版本。

Collector_of_ParNew_and_SerialOld

-XX:+UseConcMarkSweepGC ,默认新生代收集器,使用 ParNew & CMS(serial old 为替补)收集器。

-XX:+UseParNewGC ,强制使用 ParNew。

-XX:ParallelGCThreads ,限制垃圾收集器的线程数。

# Parallel Scavenge

新生代收集器、并行多线程、复制算法

特点是:达到一个可控制的吞吐量,即:

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

可高效率利用 CPU 时间。适合在后台运算,少交互任务。

-XX:MaxGCPauseMillis ,控制最大垃圾收集停顿时间。大于 0 的毫秒数。

-XX:GCTimeRatio ,设置吞吐量大小。(0,100)范围的整数,垃圾收集时间占总时间的比率。

-XX:+UseAdaptiveSizePolicy ,开启这个参数时,可以不用考虑新生代的大小( -Xmn )、Eden 与 Survivor 的比例 ( -XX:SurvivorRatio )、晋升老年代对象年龄( -XX:PretenureSizeThreshold )等参数。

只需要开启 -Xmx (最大堆),MaxGCPauseMillis 或 GCTimeRatio 即可。

# Serial Old 收集器

是 Serial 的老年代版本,同样是一个单线程收集器,使用 “标记 - 整理” 算法。

Colector_of_Serial_and_SerialOld

# Parallel Old 收集器

是 Parallel Scavenge 的老年代版本,使用多线程和 “标记 - 整理” 算法。

JDK 1.6 才开始提供,在此之前,新生代 Parallel Scavenge 只能搭配 Serial Old 使用。

Collector_of_ParallelScavenge_and_ParallelOld

# CMS 收集器

并发标记清除(Concurrent Mark Sweep)收集器,获取最短回收停顿为目标的收集器。

B/S 系统服务器端等重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

由名字看出,使用 “标记 - 清除” 算法。

工作过程步骤:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)

步骤 1 和 3 仍需要 “Stop The World”,暂停用户线程。

Colector_of_Concurrent_Mark_Sweep

缺点:

  • CMS 默认启动的回收线程数是(CPU 数量 + 3)/4,当 CPU 数量少时,对 CPU 负载较大。
  • 无法处理浮动垃圾(Floating Garbage),可能出现 “Concurrent Mode Failure” 失败导致另一次 Full GC 的产生。
    • -XX:CMSInitiatingOccupancyFraction ,修改触发百分比。设置太高容易导致频繁失败,然后临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,性能反而降低。
  • 该算法特点:空间碎片过多。
    • -XX:+UseCMSCompactAtFullCollection ,默认开启,进行 Full GC 时,开启内存碎片合并整理过程。该过程无法并发,导致停顿时间变长。
    • -XX:CMSFullGCsBeforeCompaction ,n 次 Full GC 后,才进行一次带压缩。默认为 0,即每次 FullGC 都带碎片整理。

# G1 收集器

特点:

  • 并行与并发;
  • 分代收集;
  • 空间整合;不会产生内存空间碎片。
  • 可预测的停顿;

G1 收集器将整个 Java 堆划分为多个大小相等的独立区域(Region),新生代与老年代不再是物理隔离的。

后台维护优先队列,每次根据允许的收集时间,优先回收价值最大的 Region。

  • Minor GC:回收年轻代;
  • Major GC:回收老年代;
  • Full GC:回收年轻代与老年代。

每个 Region 都有与之对应的 Remembered Set。

程序对 Reference 类型数据写操作时,会产生一个 Write barrier 暂时中断写操作;

然后检查 Reference 引用的对象处于不同的 Region 的中,便通过 CardTable 把相关引用信息记录到被引用对象的 Region 的 Remembered Set。

G1 大致划分以下步骤:

  • 初始标记(Initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting and Evacuation)

Colector_of_G1

# 理解 GC 日志

开启以下参数:

-verbose:gc ,输出 gc 信息到终端

-XX:+PrintGC ,输出日志,覆盖 -verbose:gc 参数

-XX:+PrintGCDetails ,输出 GC 具体细节

-Xloggc:<filename> ,日志文件

gc_logs_example

  • 18.019,18.037 等:JVM 启动到发生 GC 这段时间的秒数。
  • GC、Full GC 等:GC 类型。
  • [PSYoungGen: 3712K->1216K(4608K)] :GC 发生区域;GC 发生前该内存区域的使用容量 -> GC 发生后该内存区域的使用容量(该内存区域的总容量),Full GC 有多个区域
  • 16089K->13617K(18432K) ,GC 前 Java 堆使用容量 -> GC 后 Java 堆使用容量(Java 堆总容量)。
  • 0.0023505 secs ,GC 所占用时间,单位:秒。
  • [Times: user=0.01 sys=0.00, real=0.00 secs] ,用户态消耗 CPU 的时间,内核态消耗 CPU 的时间,操作从开始到结束的墙钟时间(Wall Clock Time)。
  1. 如果有多个线程时,CPU 时间是叠加的,因此 user 或 sys 大于 real 是正常的。
  2. 墙钟时间还会包括各种非运算的等待消耗,比如等待磁盘 IO,等待线程阻塞。

# 垃圾收集器参数总结

UseSerialGC
Client 模式下的默认值,使用 Serial + Serial Old 收集器组合进行内存回收。

UseParNewGC
使用 ParNew + Serial Old 收集器组合进行内存回收。

UseConcMarkSweepGC
使用 ParNew + CMS + Serial Old 收集器组合进行内存回收。Serial Old 收集器作为 CMS 出现 Concurrent Mode Failure 失败后的后备收集器使用。

UseParallelOldGC
使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收。

SurvivorRatio
新生代中 Eden 区域与 Survivor 区域的容量比值,默认为 8,代表 Eden : Survivor=8 : 1。

PretenureSizeThreshold
对象大小大于这个参数的对象将直接在老年代分配。

MaxTenuringThreshold
对象年龄超过参数值时进入老年代;每个对象坚持过一次 Minor GC 后,年龄就增加 1。

UseAdaptiveSizePolicy
动态调整 Java 堆中各个区域的大小以及进入老年代的年龄。

HandlePromotionFailure
是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况。

ParallelGCThreads
设置并行 GC 时进行内存回收的线程数。

GCTimeRatio
GC 时间占总时间的比率,默认值为 99,即允许 1% 的 GC 时间。仅使用 Parallel Scavenge 收集器时才有效。公式:GC 时间占比 = 1 / (1 + n);比如:1% = 1 / (1 + 99)。

MaxGCPauseMills
设置 GC 的最大停顿时间,仅使用 Parallel Scavenge 收集器有效。

CMSInitiatingOccupancyFraction
CMS 收集器在老年代空间被使用多少后触发垃圾收集。默认值 68%。仅使用 CMS 收集器有效。

UseCMSCompactAtFullCollection
CMS 收集器在完成垃圾收集后进行一次内存碎片整理。仅使用 CMS 收集器有效。

CMSFullGCsBeforeCompaction
CMS 收集器进行若干次垃圾收集后再启动一次内存碎片整理。仅使用 CMS 收集器有效。

# 内存分配与回收策略

# 对象优先在 Eden 分配

Java 堆大小 20M;不可扩展;分配给新生代 10M,剩下 10M 分配给老年代,新生代中的 1 个 Eden 和 2 个 Survivor 空间分配 10M,Eden:Survivor = 8:1。

步骤:

  1. 分配 allocation4 时,新生代内存不足;执行 Minor GC 回收新生代。
  2. 回收时,发现 allocation1、allocation2、allocation3 无法全部移到 Survivor 空间,所以只好通过分配担保机制提前进入老年代。
public class TestAllocation {
    private static final int _1MB = 1024 * 1024;
    /**
     * jvm args: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     */
    public static void testAllocation() {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];
    }
    public static void main(String[] args) {
        TestAllocation.testAllocation();
    }
}
[GC (Allocation Failure) [PSYoungGen: 6364K->688K(9216K)] 6364K->4792K(19456K), 0.0044968 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 7071K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 77% used [0x00000007bf600000,0x00000007bfc3bcc0,0x00000007bfe00000)
  from space 1024K, 67% used [0x00000007bfe00000,0x00000007bfeac010,0x00000007bff00000)
  to   space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
 ParOldGen       total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)
 Metaspace       used 3312K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 365K, capacity 388K, committed 512K, reserved 1048576K

新生代,Minor GC 频繁;

老年代,Major GC 比 Minor GC 一般慢 10 倍以上。

# 大对象直接进入老年代

需要大量连续内存空间的 Java 对象,比如很长的字符串以及数组。

避免一群 “朝生夕灭” 的 “短命大对象”。

-XX:PretenureSizeThreshold ,大于该值,直接在老年代分配。避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制(新生代采用复制算法)。

参数只对 Serial 和 ParNew 收集器有效。设置时,单位为字节。

allocation 直接分配在老年代,占用 40%。

public class TestPretenureThreshold {
    private static final int _1MB = 1024 * 1024;
    /**
     * jvm args: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     * -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC
     */
    public static void testPretenureSizeThreshold() {
        byte[] allocation;
        allocation = new byte[4 * _1MB]; // 直接分配在老年代中
    }
    public static void main(String[] args) {
        TestPretenureThreshold.testPretenureSizeThreshold();
    }
}
Heap
 par new generation   total 9216K, used 2433K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  29% used [0x00000007bec00000, 0x00000007bee60430, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
 Metaspace       used 3298K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K

# 长期存活的对象将进入老年代

虚拟机给每个对象定义一个年龄(Age)计数器。

如果对象在 Eden 区出生并经过一次 Minor GC 后仍存活;并且能被 Survivor 区容纳,将被移动到 Survivor 空间中,并且增加(+1)对象年龄。

对象年龄达到一定程度(默认 15),将被晋升到老年代中。 -XX:MaxTenuringThreshold设置
该参数只是设置年龄的上界,但并不一定就是达到该阈值才被移动到老年代,所以下面测试可能会发生 Survivor 区占用仍为 0%,具体参考 " 动态对象年龄判定 "。

public class TestTenuringThreshold {
    private static final int _1MB = 1024 * 1024;
    /**
     * jvm args: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     * -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution -XX:+UseSerialGC
     */
    public static void testTenuringThreshold() {
        byte[] allocation1, allocation2, allocation3;
        allocation1 = new byte[_1MB / 4];
        allocation2 = new byte[4 * _1MB];
        allocation3 = new byte[4 * _1MB];
        allocation3 = null;
        allocation3 = new byte[4 * _1MB];
    }
    public static void main(String[] args) {
        TestTenuringThreshold.testTenuringThreshold();
    }
}

MaxTenuringThreshold=1

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     840768 bytes,     840768 total
: 6620K->821K(9216K), 0.0063726 secs] 6620K->4917K(19456K), 0.0064165 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:       2128 bytes,       2128 total
: 5001K->2K(9216K), 0.0016684 secs] 9097K->4869K(19456K), 0.0016967 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4262K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  52% used [0x00000007bec00000, 0x00000007bf0290e0, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400850, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 4867K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  47% used [0x00000007bf600000, 0x00000007bfac0e70, 0x00000007bfac1000, 0x00000007c0000000)
 Metaspace       used 3298K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K

MaxTenuringThreshold=15

[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age   1:     841368 bytes,     841368 total
: 6620K->821K(9216K), 0.0057211 secs] 6620K->4917K(19456K), 0.0057660 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age   1:      25128 bytes,      25128 total
: 5002K->24K(9216K), 0.0019514 secs] 9098K->4892K(19456K), 0.0019775 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 4284K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  52% used [0x00000007bec00000, 0x00000007bf0290e0, 0x00000007bf400000)
  from space 1024K,   2% used [0x00000007bf400000, 0x00000007bf406228, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 4868K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  47% used [0x00000007bf600000, 0x00000007bfac1020, 0x00000007bfac1200, 0x00000007c0000000)
 Metaspace       used 3312K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 365K, capacity 388K, committed 512K, reserved 1048576K

# 动态对象年龄判定

如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到 MaxTenuringThreshold 要求的年龄。

TargetSurvivorRatio 也会影响运行过程中虚拟机计算的晋升阈值。

且这种晋升方式,适合 Serial 和 ParNew 收集器,其它的方式不一样。

# 空间分配担保

条件:

  1. 发生 Minior GC 之前,JVM 先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立,那么 Minor GC 可以确保安全;不用担保。
  2. 如果不成立,虚拟机查看 HandlePromotionFailure 设置值是否允许担保失败;
  3. 如果允许,会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小;
  4. 如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 有风险(Minor GC 失败后重新进行 Full GC);
  5. 如果小于,或者 HandlePromotionFailure 设置不允许,则进行一次 Full GC。

HandlePromotion

JDK 6 Update 24 之后,HandlePromotionFailure 参数就不再使用了;

规则改成:只要老年代的连续空间大于新生代对象总大小或历次晋升的平均大小就会进行 Minor GC,否则将进行 Full GC。