本问题参考自: www.zhihu.com/question/43… 解答为个人原创
Key Takeaway
- Java 默认启用了分代 GC
- 启用分代 GC 的,在发生 Young GC,更准确地说是在 Survivor 区复制的时候,存活的对象的分代年龄会加1。
- 当分代年龄 = -XX:MaxTenuringThreshold 指定的大小时,对象进入老年代
- 还有动态晋升到老年代的机制,首先根据 -XX:TargetSurvivorRatio (默认 50,也就是 50%) 指定的比例,乘以 survivor 一个区的大小,得出目标晋升空间大小。然后将分代对象大小,按照分代年龄从小到大相加,直到大于目标晋升空间大小。之后,将得出的这个分代年龄以上的对象全部晋升。
- 对于一些的 GC 算法,还可能直接在老年代上面分配,例如 G1 GC 中的 humongous allocations(大对象分配),就是对象在超过 Region 一半大小的时候,直接在老年代的连续空间分配。
对象分配
我们一般认为 Java 中 new 的对象都是在堆上分配,这个说法不够准确,应该是大部分对象在堆上的 TLAB分配,还有一部分在 栈上分配 或者是 堆上直接分配,可能 Eden 区也可能年老代。同时,对于一些的 GC 算法,还可能直接在老年代上面分配,例如 G1 GC 中的 humongous allocations(大对象分配),就是对象在超过 Region 一半大小的时候,直接在老年代的连续空间分配。
这里,我们先只关心 TLAB 分配。 对于单线程应用,每次分配内存,会记录上次分配对象内存地址末尾的指针,之后分配对象会从这个指针开始检索分配。这个机制叫做 bump-the-pointer (撞针)。 对于多线程应用来说,内存分配需要考虑线程安全。最直接的想法就是通过全局锁,但是这个性能会很差。为了优化这个性能,我们考虑可以每个线程分配一个线程本地私有的内存池,然后采用 bump-the-pointer 机制进行内存分配。这个线程本地私有的内存池,就是 TLAB。只有 TLAB 满了,再去申请内存的时候,需要扩充 TLAB 或者使用新的 TLAB,这时候才需要锁。这样大大减少了锁使用。
更详细的 TLAB 理解,请参考: 通过 JFR 与日志深入探索 JVM - TLAB 原理详解
分代年龄
分代年龄位于对象头中,用于分代 GC.记录分代年龄一共 4 bit,所以最大为 2^4 - 1 = 15。所以配置最大分代年龄-XX:MaxTenuringThreshold=n
这个n不能大于16,当然也不能小于 0.等于 0 的话,就直接入老年代。等于 16 的话(但是不能设置为 16 哟),就是从不进入老年代。默认是 15。
在发生 Young GC,更准确地说是在 Survivor 区复制的时候,存活的对象的分代年龄会加1。我们编写程序测试下,由于 编译器会优化代码,同时调用System.gc()
并不是立刻触发 GC,并且是 Full GC,可能会使对象直接进入老年代,分代年龄不再增长,所以我们可以使用 volatile 属性辅助我们真正创建对象,避免编译器优化:
1 | ini复制代码static volatile Object consumer; |
可以配合 GC 日志一起观察,关于 JVM 日志配置可以参考这篇文章:OpenJDK 11 JVM日志相关参数解析与使用
首先我们用这个参数运行程序-Xmx128m -Xlog:gc=info
,输出:
1 | ini复制代码[0.016s][info][gc] Using G1 |
可以看到,在第 15 次 GC 的时候,对象进入了老年代,内存地址不再随着 Young GC 的进行而变化。 更对对象头的详细信息,请参考:Java GC详解 - 1. 最全面的理解Java对象结构 - 对象指针 OOPs
动态晋升
动态晋升首先根据 TargetSurvivorRatio 指定的比例,乘以 survivor 一个区的大小,得出目标晋升空间大小。然后将分代对象大小,按照分代年龄从小到大相加,直到大于目标晋升空间大小。之后,将得出的这个分代年龄以上的对象全部晋升。
动态修改 Tenuring Threshold,也就是晋升的分代年龄,源代码对应:src/hotspot/share/gc/serial/defNewGeneration.cpp
1 | scss复制代码void DefNewGeneration::adjust_desired_tenuring_threshold() { |
计算目标 Tenuring Threshold 对应源码:src/hotspot/share/gc/shared/ageTable.cpp
1 | ini复制代码uint AgeTable::compute_tenuring_threshold(size_t desired_survivor_size) { |
每日一刷,轻松提升技术,斩获各种offer:
本文转载自: 掘金