【从0到1学习JVM · 15】对象在堆里来回倒腾,究竟怎样才会进老年代?

前言

我们在写 Java 时随手 new 一个对象,几乎从不关心它到底存活多久、在内存里搬过几次家。很多人只知道堆被划成了新生代和老年代,但一个对象从出生到最终进老年代,中间到底经历了什么?本文把对象在各区域的完整流转过程讲透。


文章目录

一、新对象刚出生,为什么全挤在伊甸园区

写业务代码时,绝大多数对象都是在方法内部创建的局部变量:

java 复制代码
package com.crayontech.jvm;

public class ObjectAllocationDemo {
    public void handleOrder() {
        OrderContext context = new OrderContext();
        context.validate();
    }
}

执行这几行代码时,new OrderContext() 产生的对象实体绝大多数情况下都会直接进入新生代的 Eden(伊甸园)区。

1.1 大多数对象其实活不过三秒

JVM 之所以特意划分出一个 Eden 区,是因为弱分代假说(Weak Generational Hypothesis)指出的客观规律:绝大多数 Java 对象的生命周期都极其短暂,朝生夕死。

比如你在一个方法里循环查库、拼接 JSON、处理业务 DTO,方法执行完毕退出栈帧,这些对象立刻失去所有的引用链,变成了垃圾。

如果每次 new 对象都直接混放在一整块大内存里,垃圾回收器扫描和整理内存的开销会难以承受。JVM 的设计者干脆把新生代划出单独一块区域,专门用来接纳这些新生的短命对象。
#mermaid-svg-nla2exxqLfG3Rtd5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nla2exxqLfG3Rtd5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nla2exxqLfG3Rtd5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nla2exxqLfG3Rtd5 .error-icon{fill:#552222;}#mermaid-svg-nla2exxqLfG3Rtd5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nla2exxqLfG3Rtd5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nla2exxqLfG3Rtd5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nla2exxqLfG3Rtd5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nla2exxqLfG3Rtd5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nla2exxqLfG3Rtd5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nla2exxqLfG3Rtd5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nla2exxqLfG3Rtd5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nla2exxqLfG3Rtd5 .marker.cross{stroke:#333333;}#mermaid-svg-nla2exxqLfG3Rtd5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nla2exxqLfG3Rtd5 p{margin:0;}#mermaid-svg-nla2exxqLfG3Rtd5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nla2exxqLfG3Rtd5 .cluster-label text{fill:#333;}#mermaid-svg-nla2exxqLfG3Rtd5 .cluster-label span{color:#333;}#mermaid-svg-nla2exxqLfG3Rtd5 .cluster-label span p{background-color:transparent;}#mermaid-svg-nla2exxqLfG3Rtd5 .label text,#mermaid-svg-nla2exxqLfG3Rtd5 span{fill:#333;color:#333;}#mermaid-svg-nla2exxqLfG3Rtd5 .node rect,#mermaid-svg-nla2exxqLfG3Rtd5 .node circle,#mermaid-svg-nla2exxqLfG3Rtd5 .node ellipse,#mermaid-svg-nla2exxqLfG3Rtd5 .node polygon,#mermaid-svg-nla2exxqLfG3Rtd5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nla2exxqLfG3Rtd5 .rough-node .label text,#mermaid-svg-nla2exxqLfG3Rtd5 .node .label text,#mermaid-svg-nla2exxqLfG3Rtd5 .image-shape .label,#mermaid-svg-nla2exxqLfG3Rtd5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-nla2exxqLfG3Rtd5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nla2exxqLfG3Rtd5 .rough-node .label,#mermaid-svg-nla2exxqLfG3Rtd5 .node .label,#mermaid-svg-nla2exxqLfG3Rtd5 .image-shape .label,#mermaid-svg-nla2exxqLfG3Rtd5 .icon-shape .label{text-align:center;}#mermaid-svg-nla2exxqLfG3Rtd5 .node.clickable{cursor:pointer;}#mermaid-svg-nla2exxqLfG3Rtd5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nla2exxqLfG3Rtd5 .arrowheadPath{fill:#333333;}#mermaid-svg-nla2exxqLfG3Rtd5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nla2exxqLfG3Rtd5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nla2exxqLfG3Rtd5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nla2exxqLfG3Rtd5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nla2exxqLfG3Rtd5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nla2exxqLfG3Rtd5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nla2exxqLfG3Rtd5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nla2exxqLfG3Rtd5 .cluster text{fill:#333;}#mermaid-svg-nla2exxqLfG3Rtd5 .cluster span{color:#333;}#mermaid-svg-nla2exxqLfG3Rtd5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nla2exxqLfG3Rtd5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nla2exxqLfG3Rtd5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-nla2exxqLfG3Rtd5 .icon-shape,#mermaid-svg-nla2exxqLfG3Rtd5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nla2exxqLfG3Rtd5 .icon-shape p,#mermaid-svg-nla2exxqLfG3Rtd5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nla2exxqLfG3Rtd5 .icon-shape .label rect,#mermaid-svg-nla2exxqLfG3Rtd5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nla2exxqLfG3Rtd5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nla2exxqLfG3Rtd5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nla2exxqLfG3Rtd5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} JVM 堆内存 (Heap)
老年代 (Old Generation)
新生代 (Young Generation)
绝大多数新对象
存活对象转移
复制轮转
晋升
晋升
Eden 区 (伊甸园区)

新对象优先在此分配
Survivor 0 (From)
Survivor 1 (To)
Old 区

存放长期存活或大对象
代码执行: new Object()

1.2 伊甸园区满了会发生什么

Eden 区的容量是有限的。随着业务线程持续不断地创建对象,Eden 区很快就会被填满。

当又有新对象申请分配内存,而 Eden 区剩余空间不足时,JVM 就会触发一次 Minor GC(也叫 Young GC)。

这次 GC 的首要任务就是快速清理新生代:

  1. 找出 Eden 区中所有失去根引用的垃圾对象。
  2. 将这些垃圾对象占用的内存空间就地释放。
  3. 找出少量依然存活的对象,准备把它们转移到幸存者区(Survivor)。

这个过程速度极快,因为新生代里存活的对象通常不足 10%,GC 只需要关注并拷贝那 10% 的活对象,剩下的 90% 垃圾直接覆盖清空。

二、存活下来的对象,为什么要在两个幸存区来回跳

经历了第一次 Minor GC 依然存活的对象,并不会立刻被送到老年代,而是先被复制到了 Survivor 区。

Survivor 区又被平分成了两块:S0(From Survivor)和 S1(To Survivor)。

2.1 标记复制算法与双幸存区的配合

新生代采用的是标记-复制(Mark-Copy)算法。这种算法的最大优点是运行效率高、没有内存碎片,但代价是必须有一块空闲内存用来接收存活对象。

S0 和 S1 的空间大小始终完全一致,而在任意时刻,它们当中必定有一块是保持完全空白的:

  1. 第一次 Minor GC:Eden 区满了,存活的对象被复制到 S0,此时 S1 是空的。存活下来的对象年龄被记为 1。Eden 区被瞬间清空。
  2. 第二次 Minor GC :经过一段时间运行,Eden 区又满了。此时 GC 会同时扫描 Eden 区与 S0 区。将两处所有存活的对象统一复制到此前完全空置的 S1 区。原先在 S0 里的对象如果还活着用,年龄累加变成 2;从 Eden 刚搬过来的新对象年龄记为 1。随后 Eden 区和 S0 区被整体清空。
  3. 后续轮转:此时 S0 变成了空的,S1 存放着存活对象。下一次 GC 时,存活对象又会从 S1 复制回 S0。

老年代 S1 (To) S0 (From) Eden 区 业务线程 老年代 S1 (To) S0 (From) Eden 区 业务线程 #mermaid-svg-tT1QcsJrdJgec0sd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tT1QcsJrdJgec0sd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tT1QcsJrdJgec0sd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tT1QcsJrdJgec0sd .error-icon{fill:#552222;}#mermaid-svg-tT1QcsJrdJgec0sd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tT1QcsJrdJgec0sd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tT1QcsJrdJgec0sd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tT1QcsJrdJgec0sd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tT1QcsJrdJgec0sd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tT1QcsJrdJgec0sd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tT1QcsJrdJgec0sd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tT1QcsJrdJgec0sd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tT1QcsJrdJgec0sd .marker.cross{stroke:#333333;}#mermaid-svg-tT1QcsJrdJgec0sd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tT1QcsJrdJgec0sd p{margin:0;}#mermaid-svg-tT1QcsJrdJgec0sd .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tT1QcsJrdJgec0sd text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tT1QcsJrdJgec0sd .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tT1QcsJrdJgec0sd .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tT1QcsJrdJgec0sd .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tT1QcsJrdJgec0sd .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tT1QcsJrdJgec0sd #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tT1QcsJrdJgec0sd .sequenceNumber{fill:white;}#mermaid-svg-tT1QcsJrdJgec0sd #sequencenumber{fill:#333;}#mermaid-svg-tT1QcsJrdJgec0sd #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tT1QcsJrdJgec0sd .messageText{fill:#333;stroke:none;}#mermaid-svg-tT1QcsJrdJgec0sd .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tT1QcsJrdJgec0sd .labelText,#mermaid-svg-tT1QcsJrdJgec0sd .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tT1QcsJrdJgec0sd .loopText,#mermaid-svg-tT1QcsJrdJgec0sd .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tT1QcsJrdJgec0sd .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tT1QcsJrdJgec0sd .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tT1QcsJrdJgec0sd .noteText,#mermaid-svg-tT1QcsJrdJgec0sd .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tT1QcsJrdJgec0sd .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tT1QcsJrdJgec0sd .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tT1QcsJrdJgec0sd .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tT1QcsJrdJgec0sd .actorPopupMenu{position:absolute;}#mermaid-svg-tT1QcsJrdJgec0sd .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tT1QcsJrdJgec0sd .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tT1QcsJrdJgec0sd .actor-man circle,#mermaid-svg-tT1QcsJrdJgec0sd line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tT1QcsJrdJgec0sd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 触发第 1 次 Minor GC (S1 当前为空) Eden 垃圾被清空 触发第 2 次 Minor GC (S0 变 From,S1 变 To) Eden 与 S0 全部清空归零 持续创建对象,直到 Eden 满 1 存活对象拷贝到 S0 (Age=1) 2 继续创建对象,Eden 再次满 3 Eden 存活对象拷贝到 S1 (Age=1) 4 S0 存活对象拷贝到 S1 (Age=2) 5

两个幸存区就像两个倒换的抽屉,一个负责装东西,另一个必须腾空待命。只要不断在两者之间对调拷贝,新生代永远不会产生任何内存碎片,空间利用非常规整。

2.2 年龄计数器与四位二进制限制

对象每次成功熬过一轮 Minor GC,并在 Survivor 区之间完成一次拷贝,它内部记录的年龄就会增加 1。

这个年龄存放在哪里?它就记录在 Java 对象头(Object Header)的 Mark Word 里面。

在 HotSpot 虚拟机中,Mark Word 只给对象分代年龄分配了 4 位(4 bits) 的存储空间。
#mermaid-svg-NCCdCTLGrsm9xuTN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NCCdCTLGrsm9xuTN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NCCdCTLGrsm9xuTN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NCCdCTLGrsm9xuTN .error-icon{fill:#552222;}#mermaid-svg-NCCdCTLGrsm9xuTN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NCCdCTLGrsm9xuTN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NCCdCTLGrsm9xuTN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NCCdCTLGrsm9xuTN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NCCdCTLGrsm9xuTN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NCCdCTLGrsm9xuTN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NCCdCTLGrsm9xuTN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NCCdCTLGrsm9xuTN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NCCdCTLGrsm9xuTN .marker.cross{stroke:#333333;}#mermaid-svg-NCCdCTLGrsm9xuTN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NCCdCTLGrsm9xuTN p{margin:0;}#mermaid-svg-NCCdCTLGrsm9xuTN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NCCdCTLGrsm9xuTN .cluster-label text{fill:#333;}#mermaid-svg-NCCdCTLGrsm9xuTN .cluster-label span{color:#333;}#mermaid-svg-NCCdCTLGrsm9xuTN .cluster-label span p{background-color:transparent;}#mermaid-svg-NCCdCTLGrsm9xuTN .label text,#mermaid-svg-NCCdCTLGrsm9xuTN span{fill:#333;color:#333;}#mermaid-svg-NCCdCTLGrsm9xuTN .node rect,#mermaid-svg-NCCdCTLGrsm9xuTN .node circle,#mermaid-svg-NCCdCTLGrsm9xuTN .node ellipse,#mermaid-svg-NCCdCTLGrsm9xuTN .node polygon,#mermaid-svg-NCCdCTLGrsm9xuTN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NCCdCTLGrsm9xuTN .rough-node .label text,#mermaid-svg-NCCdCTLGrsm9xuTN .node .label text,#mermaid-svg-NCCdCTLGrsm9xuTN .image-shape .label,#mermaid-svg-NCCdCTLGrsm9xuTN .icon-shape .label{text-anchor:middle;}#mermaid-svg-NCCdCTLGrsm9xuTN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NCCdCTLGrsm9xuTN .rough-node .label,#mermaid-svg-NCCdCTLGrsm9xuTN .node .label,#mermaid-svg-NCCdCTLGrsm9xuTN .image-shape .label,#mermaid-svg-NCCdCTLGrsm9xuTN .icon-shape .label{text-align:center;}#mermaid-svg-NCCdCTLGrsm9xuTN .node.clickable{cursor:pointer;}#mermaid-svg-NCCdCTLGrsm9xuTN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NCCdCTLGrsm9xuTN .arrowheadPath{fill:#333333;}#mermaid-svg-NCCdCTLGrsm9xuTN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NCCdCTLGrsm9xuTN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NCCdCTLGrsm9xuTN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NCCdCTLGrsm9xuTN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NCCdCTLGrsm9xuTN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NCCdCTLGrsm9xuTN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NCCdCTLGrsm9xuTN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NCCdCTLGrsm9xuTN .cluster text{fill:#333;}#mermaid-svg-NCCdCTLGrsm9xuTN .cluster span{color:#333;}#mermaid-svg-NCCdCTLGrsm9xuTN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NCCdCTLGrsm9xuTN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NCCdCTLGrsm9xuTN rect.text{fill:none;stroke-width:0;}#mermaid-svg-NCCdCTLGrsm9xuTN .icon-shape,#mermaid-svg-NCCdCTLGrsm9xuTN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NCCdCTLGrsm9xuTN .icon-shape p,#mermaid-svg-NCCdCTLGrsm9xuTN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NCCdCTLGrsm9xuTN .icon-shape .label rect,#mermaid-svg-NCCdCTLGrsm9xuTN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NCCdCTLGrsm9xuTN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NCCdCTLGrsm9xuTN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NCCdCTLGrsm9xuTN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 64位 Mark Word (无锁状态)
未使用 (25 bit)
对象哈希码 (31 bit)
分代年龄 (4 bit)

0000 ~ 1111
偏向锁位 (1 bit)
锁标志位 (2 bit)

4 位无符号二进制数的最大值是:

2 4 − 1 = 15 ( 1111 2 ) 2^4 - 1 = 15 \quad (1111_2) 24−1=15(11112)

这就是无论你怎么设置 JVM 启动参数,-XX:MaxTenuringThreshold 的最大值绝对不能超过 15 的根本原因。底层硬件和数据结构已经把上限卡死在 15 了。

三、对象进入老年代的三条硬核路线

对象一直在两个 Survivor 区跳来跳去,不可能无限期跳下去。老年代就是为了存放长生命周期对象而设计的。

一个对象进入老年代,主要通过以下三种方式。

3.1 活过15次垃圾回收的常规晋升

这是最经典的晋升路线。

如果一个对象在 S0 和 S1 之间来回倒腾了 15 次,历经 15 轮 Minor GC 依然没有被回收,说明它大概率是系统常驻的核心组件,比如 Spring 容器中的单例 Bean、数据库连接池、全局静态配置缓存等。

当它的年龄达到阈值(默认 15)后,下一次 Minor GC 如果它依然存活,垃圾回收器就不会再把它拷到另一个 Survivor 区,而是直接送入老年代。

java 复制代码
package com.crayontech.jvm;

// 这类生命周期极长的对象,经过多次 GC 最终会常驻老年代
public class GlobalConfigHolder {
    private static final GlobalConfigHolder INSTANCE = new GlobalConfigHolder();

    private GlobalConfigHolder() {}

    public static GlobalConfigHolder getInstance() {
        return INSTANCE;
    }
}

3.2 幸存区放不下的动态提前晋升

实际生产环境中,很多对象根本不需要等满 15 岁,就会被提前送进老年代。

这涉及两个核心机制:

  1. Survivor 空间放不下 :
    Survivor 区的容量通常只有 Eden 的八分之一左右。如果某次 Minor GC 之后,Eden 和 From Survivor 存活下来的对象体积比较大,目标 To Survivor 根本装不下。此时 JVM 的分配担保机制会启动,装不下的这部分存活对象会被直接送到老年代。
  2. 动态对象年龄判断(Dynamic Age Calculation) :
    HotSpot 虚拟机有一条规则:如果在 Survivor 空间中,相同年龄所有对象大小的总和大于 Survivor 空间的一半(可以通过 -XX:TargetSurvivorRatio 调整,默认 50%),那么年龄大于或等于该年龄的对象,就可以直接进入老年代,完全不用等 15 岁。

这样做是为了防止 Survivor 区被过快撑爆,提前为年轻代腾出周转空间。
#mermaid-svg-cJSLAoJLh9axFeki{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cJSLAoJLh9axFeki .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cJSLAoJLh9axFeki .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cJSLAoJLh9axFeki .error-icon{fill:#552222;}#mermaid-svg-cJSLAoJLh9axFeki .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cJSLAoJLh9axFeki .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cJSLAoJLh9axFeki .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cJSLAoJLh9axFeki .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cJSLAoJLh9axFeki .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cJSLAoJLh9axFeki .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cJSLAoJLh9axFeki .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cJSLAoJLh9axFeki .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cJSLAoJLh9axFeki .marker.cross{stroke:#333333;}#mermaid-svg-cJSLAoJLh9axFeki svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cJSLAoJLh9axFeki p{margin:0;}#mermaid-svg-cJSLAoJLh9axFeki .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cJSLAoJLh9axFeki .cluster-label text{fill:#333;}#mermaid-svg-cJSLAoJLh9axFeki .cluster-label span{color:#333;}#mermaid-svg-cJSLAoJLh9axFeki .cluster-label span p{background-color:transparent;}#mermaid-svg-cJSLAoJLh9axFeki .label text,#mermaid-svg-cJSLAoJLh9axFeki span{fill:#333;color:#333;}#mermaid-svg-cJSLAoJLh9axFeki .node rect,#mermaid-svg-cJSLAoJLh9axFeki .node circle,#mermaid-svg-cJSLAoJLh9axFeki .node ellipse,#mermaid-svg-cJSLAoJLh9axFeki .node polygon,#mermaid-svg-cJSLAoJLh9axFeki .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cJSLAoJLh9axFeki .rough-node .label text,#mermaid-svg-cJSLAoJLh9axFeki .node .label text,#mermaid-svg-cJSLAoJLh9axFeki .image-shape .label,#mermaid-svg-cJSLAoJLh9axFeki .icon-shape .label{text-anchor:middle;}#mermaid-svg-cJSLAoJLh9axFeki .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cJSLAoJLh9axFeki .rough-node .label,#mermaid-svg-cJSLAoJLh9axFeki .node .label,#mermaid-svg-cJSLAoJLh9axFeki .image-shape .label,#mermaid-svg-cJSLAoJLh9axFeki .icon-shape .label{text-align:center;}#mermaid-svg-cJSLAoJLh9axFeki .node.clickable{cursor:pointer;}#mermaid-svg-cJSLAoJLh9axFeki .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cJSLAoJLh9axFeki .arrowheadPath{fill:#333333;}#mermaid-svg-cJSLAoJLh9axFeki .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cJSLAoJLh9axFeki .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cJSLAoJLh9axFeki .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cJSLAoJLh9axFeki .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cJSLAoJLh9axFeki .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cJSLAoJLh9axFeki .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cJSLAoJLh9axFeki .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cJSLAoJLh9axFeki .cluster text{fill:#333;}#mermaid-svg-cJSLAoJLh9axFeki .cluster span{color:#333;}#mermaid-svg-cJSLAoJLh9axFeki div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cJSLAoJLh9axFeki .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cJSLAoJLh9axFeki rect.text{fill:none;stroke-width:0;}#mermaid-svg-cJSLAoJLh9axFeki .icon-shape,#mermaid-svg-cJSLAoJLh9axFeki .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cJSLAoJLh9axFeki .icon-shape p,#mermaid-svg-cJSLAoJLh9axFeki .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cJSLAoJLh9axFeki .icon-shape .label rect,#mermaid-svg-cJSLAoJLh9axFeki .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cJSLAoJLh9axFeki .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cJSLAoJLh9axFeki .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cJSLAoJLh9axFeki :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
否
是
装不下 / 超过担保容量
装得下
未达到
达到 15
创建新对象
对象是否巨大?

(是否超过 PretenureSizeThreshold)
路线三:大对象直接进老年代

(绕过 Eden 与 Survivor)
优先在 Eden 区分配
Eden 区空间不足?
正常运行工作
触发 Minor GC (Young GC)
Survivor (To) 装得下存活对象吗?
路线二:空间担保/动态年龄超标

直接提前晋升老年代
拷贝到 Survivor (To) 区

年龄计数 Age + 1
Age 是否达到阈值?

(默认 15 岁)
在 S0 与 S1 间继续轮转
路线一:满 15 岁常规晋升老年代
老年代 (Old Gen)

3.3 大对象直接跨过新生代进入老年代

还有一种最霸道的路线:对象刚被 new 出来,连 Eden 区的门都还没进,就直接落到了老年代。

典型的大对象包括超长的数组、大字符串或者大文件字节缓冲区:

java 复制代码
package com.crayontech.jvm;

public class LargeObjectDemo {
    public void allocateBuffer() {
        // 分配一个占用 8MB 的连续大字节数组
        byte[] largeBuffer = new byte[8 * 1024 * 1024];
    }
}

如果在 JVM 中配置了参数 -XX:PretenureSizeThreshold(只在 Serial 和 ParNew 收集器下生效),只要创建的对象体积超过了这个设定值,JVM 就会把它直接分配在老年代。

这么设计的原因很直接:大对象需要大块连续的内存空间。如果把大对象放在 Eden 区,在 Survivor 之间来回拷贝时会带来巨大的内存复制性能开销;更糟的是,它很容易直接把小巧的 Survivor 区挤爆,导致本不该进老年代的短命对象跟着提前晋升,破坏分代垃圾回收的平衡。

四、写在最后

弄清楚对象在堆中的流转过程,平时看 GC 日志和排查内存问题时思路就会清晰很多:

  • Eden 区是高频产生垃圾、高频清空的第一现场。
  • 双 Survivor 区用极高的规整度为存活对象提供了缓冲周转,用时间换空间,筛选出真正的长寿对象。
  • 老年代则是压舱石,只有真正历经考验的长寿对象、或者天生巨大的特殊对象才有资格进驻。

如果这篇文章对你理解 JVM 堆内存流转有所帮助,欢迎关注作者,后续我们继续把垃圾回收算法和 GC 调优实战彻底拆透。

相关推荐
一条小小yu1 小时前
为什么使用springboot
java·spring boot·后端
Elastic 中国社区官方博客1 小时前
Kubernetes attributes processor v1:它对 EDOT Collector 意味着什么
java·大数据·elasticsearch·搜索引擎·贪心算法·kubernetes·全文检索
Sarvartha1 小时前
对象创建与引用
java·开发语言
大侠归来2 小时前
C语言与C++在基础语法上的区别
java·c语言·c++
小宋10212 小时前
SQL + RAG 混合问答实战:结构化指标与文档证据如何统一路由
java·jvm·人工智能·sql
bro_Java6663 小时前
链表进阶2:双向链表的构建
java·数据结构·链表
软件开发技术深度爱好者3 小时前
中学生学习工具:多学科自学启导者(轻量版)实现
学习·ai·技术实践
DianSan_ERP3 小时前
多平台订单自动下载与回传的技术实现:从消息推送到状态闭环引言
java·linux·服务器·前端·网络·架构·自动化
Joe_Wang53 小时前
【从0到1学习JVM · 13】点进JDK源码只有一个分号?搞懂本地方法栈与JNI机制
java·jvm·学习·本地方法栈