JVM 四大晋升机制

对于JVM来说,有四种晋升机制。


何为对象晋升

在 Java 虚拟机(JVM)的内存管理中,绝大多数垃圾收集器(如 Serial, Parallel Scavenge, CMS, G1)都采用了分代收集理论。该理论的核心思想是根据对象的存活时间将内存划分为不同的代中,并针对不同代的特点采用不同的垃圾收集算法。

现代 JVM 通常将堆内存划分为两块:

  • 新生代 (Young Generation) :存放新创建的对象。其特点是"朝生夕死",大部分对象很快就会变得不可达。新生代是垃圾收集发生最频繁的区域,采用的收集算法是复制算法
  • 老年代 (Tenured/Old Generation):存放存活时间较长的对象。特点是对象存活率高,回收频率相对较低。

当一个对象在新生代中经历了多次垃圾收集后仍然存活,它就会被移动到老年代。这个过程就称为对象晋升


常规晋升规则

JVM有两种最常见的晋升规则

年龄阈值 (MaxTenuringThreshold)

这是最直观的晋升规则。新生代(特别是 HotSpot 虚拟机)被划分为一个 Eden 区和两个 Survivor 区(S0S1)。对象首次创建在 Eden 区。

  • 每次发生 Minor GCEden 区和其中一个 Survivor 区中存活的对象会被复制到另一个空的 Survivor 区。
  • 每经历过一次 Minor GC 且存活下来,对象的年龄 (Age) 就增加 1。
  • 当对象的年龄达到一个预定义的阈值(-XX:MaxTenuringThreshold,默认值通常为 15,CMS 下为 6)时,在下一次 GC 时,它就会被晋升到老年代。

示例:

bash 复制代码
# 设置晋升年龄阈值为 10
-XX:MaxTenuringThreshold=10

Survivor 空间不足导致提前晋升

如果在一次 Minor GC 后,存活对象的大小超过了目标 Survivor 区 的可用容量,JVM 为了确保这些对象有地方存放,会直接 将一部分(通常是所有无法放入 Survivor 的对象)晋升到老年代,而不管它们的年龄是否达到了 MaxTenuringThreshold。(通常使用空间担保机制)

这是一种保护机制,防止 Survivor 区被撑爆。


特殊的晋升机制

除了上述两条规则,JVM 还提供了两种非常重要且"特殊"的动态判定机制,它们是为了优化内存使用和性能而设计的。

动态年龄判定

核心思想

并非一定要等到对象的年龄达到 MaxTenuringThreshold 才晋升。如果 Survivor 空间中相同年龄的所有对象大小的总和大于其中一个 Survivor 空间的一半 ,那么年龄大于或等于该年龄 的对象就可以直接晋升到老年代。

  • 也就是说 : Survivor区中有一批对象,年龄分别为年龄1+年龄2+年龄n的多个对象,对象总和大小超过了Survivor区域的50%,此时就会把年龄n及以上的对象都放入老年代。

触发条件与流程

  • JVM 会在每次 Minor GC 后,遍历 Survivor 区中的对象。
  • 它会对所有对象的年龄进行排序,并从小到大地累加每个年龄的对象的总大小。
  • 当累加到某个年龄(例如年龄为 n)时,发现总和超过了 Survivor 区容量 (TargetSurvivorRatio 默认是 50%)
  • 此时,JVM 会认为从年龄 n 开始往后的对象已经足够"老"了,没必要再在 Survivor 区之间来回复制。
  • 于是,它会将所有年龄 >= n 的对象全部晋升到老年代。

参数影响:

  • -XX:TargetSurvivorRatio:指定 Survivor 空间使用率的阈值百分比(默认值 50)。动态年龄判定正是基于这个比率来计算目标大小的。

示例:

假设 Survivor 区总大小为 10MB,TargetSurvivorRatio=50

  • 计算目标阈值大小:10MB * 50% = 5MB
  • GC 后,JVM 发现:年龄1+年龄2+年龄3的对象总大小已经达到了 5.1MB(超过了 5MB),而年龄3的对象大小是 0.5MB。
  • 那么,JVM 会判定所有年龄 >= 3 的对象都可以直接晋升。

大对象直接进入老年代

核心思想

在 Serial 和 ParNew 这类使用复制算法的新生代收集器中,大对象(Large Object)的分配和回收是一个显著的性能痛点。

直接进入老年代原因

  • 分配开销:一个大对象需要寻找一块连续的、足够大的内存空间。如果将其分配在 Eden 区,可能会因为空间不足而提前触发 Minor GC,即使系统中还有很多零散的可用内存。
  • 复制开销:复制算法的核心是将存活对象在两个 Survivor 区之间来回复制。如果一个大对象在新生代中幸存,那么在下一次 Minor GC 时,复制这个大对象的成本会非常高(内存拷贝操作耗时)。
    -XX:PretenureSizeThreshold

避免大对象在 Eden 区及两个 Survivor 区之间进行复制,从而降低内存分配和垃圾收集的开销,提升性能。

工作机制

当 JVM 需要为一个新对象分配内存时,它会执行以下判断流程

空间分配担保

核心思想

在发生 Minor GC 之前 ,JVM 需要先检查一下老年代最大的可用连续空间 是否大于新生代所有对象的总大小 或者历次晋升到老年代对象的平均大小 。如果条件不满足,则会先尝试进行一次 Full GC 来腾出老年代空间,然后再进行 Minor GC。这个检查过程就是"空间分配担保"。

触发条件与流程

  1. 检查:Minor GC 前,JVM 检查老年代的剩余空间是否充足。
  2. 判断逻辑
    • 如果老年代的可用空间 大于 新生代所有对象的总大小(即最坏情况,所有对象都存活),则担保成功,直接进行 Minor GC。这样是安全的。
    • 否则,JVM 会查看 -XX:-HandlePromotionFailure 参数(JDK 6 Update 24 之后此参数已失效,规则被整合到 GC 逻辑中,但思想不变)以及历次晋升的平均大小
    • 如果老年代的可用空间 大于 历次晋升到老年代对象的平均大小,则冒险尝试进行一次 Minor GC。这是一种乐观估计,认为这次晋升的对象不会比平均大小多太多。
    • 如果小于,或者上一次冒险失败了,则担保失败 ,JVM 会先触发一次 Full GC 来清理老年代,然后再进行 Minor GC。
  3. 冒险后的处理 :如果冒险进行 Minor GC 后,发现需要晋升到老年代的对象大小确实大于 老年代的当前可用空间,此时担保彻底失败,会被迫 在 Minor GC 后紧接着进行一次 Full GC(promotion failed),这是代价最大的一种情况。

总结

机制 触发时机 核心目的 关键参数/影响因素
年龄阈值 Minor GC 后,对象年龄达标 常规晋升,保证对象经过充分熬炼 -XX:MaxTenuringThreshold
Survivor 不足 Minor GC 后,存活对象太多 保护机制,防止 Survivor 区溢出 Survivor 区大小、GC 后存活对象大小
动态年龄判定 Minor GC 后,某年龄段对象总大小超限 优化机制,减少复制,及时清理 -XX:TargetSurvivorRatio、对象年龄分布
空间分配担保 Minor GC ,检查老年代空间 风险控制机制,避免晋升失败(OOM) 老年代剩余空间、历次晋升平均大小
相关推荐
木心爱编程2 小时前
C++20多线程新特性:更安全高效的并发编程
java·jvm·c++20
ljf88382 小时前
Java导出复杂excel,自定义excel导出
java·开发语言·excel
江流月照2 小时前
PCIE地址空间介绍
java·服务器·网络
一只乔哇噻3 小时前
java后端工程师进修ing(研一版‖day44)
java·开发语言·学习·算法
老华带你飞3 小时前
畅阅读小程序|畅阅读系统|基于java的畅阅读系统小程序设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序·毕设·畅阅读系统小程序
卓码软件测评6 小时前
第三方软件测试机构【性能测试工具用LoadRunner还是JMeter?】
java·功能测试·测试工具·jmeter·性能优化
Lionel_SSL9 小时前
《深入理解Java虚拟机》第三章读书笔记:垃圾回收机制与内存管理
java·开发语言·jvm
记得开心一点嘛9 小时前
手搓Springboot
java·spring boot·spring
老华带你飞10 小时前
租房平台|租房管理平台小程序系统|基于java的租房系统 设计与实现(源码+数据库+文档)
java·数据库·小程序·vue·论文·毕设·租房系统管理平台