【从0到1学习JVM · 21】既不浪费空间又没有碎片,标记整理算法到底慢在哪

前言

上一篇聊完复制算法,很多同学会觉得它虽然效率极高,但浪费一部分内存的代价还是太奢侈了。面对存活率极高、动辄几个 G 甚至几十个 G 的老年代,JVM 既不能容忍满地碎片,也没办法多划一半空间出来做预留。为了解决这个死局,标记-整理算法成了老年代的标配。本文就来聊聊它是怎么做到不浪费空间又没有碎片的,以及它为什么常常被大家吐槽性能慢。


文章目录

    • 前言
    • 一、当老年代遇到两难选择
      • [1.1 为什么复制算法在老年代走不通](#1.1 为什么复制算法在老年代走不通)
      • [1.2 标记清除留下的碎片老年代更吃不消](#1.2 标记清除留下的碎片老年代更吃不消)
    • 二、标记整理的三步走流程
      • [2.1 顺着根节点找出存活对象](#2.1 顺着根节点找出存活对象)
      • [2.2 租房单间里的大扫除](#2.2 租房单间里的大扫除)
      • [2.3 存活边界划定后的一键清理](#2.3 存活边界划定后的一键清理)
    • 三、为什么大家都在说它慢
      • [3.1 内存里的物理搬家开销](#3.1 内存里的物理搬家开销)
      • [3.2 真正的大坑是指针修正](#3.2 真正的大坑是指针修正)
      • [3.3 业务线程必须被死死卡住](#3.3 业务线程必须被死死卡住)
    • 四、三大经典算法的取舍
      • [4.1 速度与空间的三角平衡](#4.1 速度与空间的三角平衡)
      • [4.2 分代收集给出的折中答案](#4.2 分代收集给出的折中答案)
    • 写在最后

一、当老年代遇到两难选择

前面两篇我们分别聊了标记-清除和复制算法。这两个算法各自有一个非常鲜明的痛点:

  • 标记-清除算法:直接就地回收死对象,不用挪动任何存活对象,速度挺快,但回收完留下一地内存碎片,大对象根本放不进去。
  • 复制算法:把活着的对象搬到另一半空闲内存里,彻底解决了碎片问题,但代价是必须预留一块专门的空间,而且存活对象一多,复制成本直接起飞。

到了老年代这里,情况彻底变了。老年代里的对象几乎全是从新生代经历过十几轮 GC 熬过来的"老油条",或者是刚出生就被大对象直接晋升上来的核心数据。这些对象的特点就两个字:命硬。

1.1 为什么复制算法在老年代走不通

很多同学在学完新生代用复制算法之后,会很自然地想:老年代能不能也用复制算法?

答案是根本做不到。

在新生代里,每次 Minor GC 活下来的对象通常只有百分之几(所谓的朝生夕死)。比如 100 MB 的 Eden 区,GC 之后大概率只剩几兆存活对象,所以 Survivor 区只要给个 10 MB 左右就能装得下。

但在老年代里,对象的存活率经常在 80% 甚至 90% 以上。如果老年代也用复制算法,会遇到两个致命死穴:

  1. 空间浪费无法承受:如果老年代有 4 GB 内存,为了做半区复制,你就得拿出 2 GB 常年空着不用。对生产环境寸土寸金的堆内存来说,这纯粹是在挥霍资源。
  2. 没有其他区域能做分配担保 :新生代之所以敢只留一小块 Survivor 区,是因为万一哪次大促存活对象暴增塞不下了,后面还有庞大的老年代可以作为"兜底担保"(直接把放不下的对象借道晋升到老年代)。但老年代已经是 JVM 内存分代的最后一环,它身后没有任何垫底的区域。一旦对象存活太多塞不下,系统只能当场直接抛出 OutOfMemoryError。

1.2 标记清除留下的碎片老年代更吃不消

既然复制算法走不通,那用标记-清除不就行了吗?

标记-清除在老年代确实有人用过(比如 CMS 垃圾收集器在并发阶段的核心思路),但它给老年代带来的灾难比新生代严重得多。

老年代里经常需要分配连续的大块内存,比如大数组、缓存单例、线程池和数据库连接池。如果老年代长期用标记-清除,跑个几天之后,整个老年代就会变成蜂窝煤一样的状态。

这时候业务突然来了一个 10 MB 的连续大数组,JVM 扫了一遍老年代,发现总剩余内存还有整整 500 MB。但在内存物理地址上,散落的全是 1 MB、2 MB 的小空隙,没有任何一块连续的区域能塞下这 10 MB。
#mermaid-svg-MinX7mXw9OTdHHH4{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-MinX7mXw9OTdHHH4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MinX7mXw9OTdHHH4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MinX7mXw9OTdHHH4 .error-icon{fill:#552222;}#mermaid-svg-MinX7mXw9OTdHHH4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MinX7mXw9OTdHHH4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MinX7mXw9OTdHHH4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MinX7mXw9OTdHHH4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MinX7mXw9OTdHHH4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MinX7mXw9OTdHHH4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MinX7mXw9OTdHHH4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MinX7mXw9OTdHHH4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MinX7mXw9OTdHHH4 .marker.cross{stroke:#333333;}#mermaid-svg-MinX7mXw9OTdHHH4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MinX7mXw9OTdHHH4 p{margin:0;}#mermaid-svg-MinX7mXw9OTdHHH4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MinX7mXw9OTdHHH4 .cluster-label text{fill:#333;}#mermaid-svg-MinX7mXw9OTdHHH4 .cluster-label span{color:#333;}#mermaid-svg-MinX7mXw9OTdHHH4 .cluster-label span p{background-color:transparent;}#mermaid-svg-MinX7mXw9OTdHHH4 .label text,#mermaid-svg-MinX7mXw9OTdHHH4 span{fill:#333;color:#333;}#mermaid-svg-MinX7mXw9OTdHHH4 .node rect,#mermaid-svg-MinX7mXw9OTdHHH4 .node circle,#mermaid-svg-MinX7mXw9OTdHHH4 .node ellipse,#mermaid-svg-MinX7mXw9OTdHHH4 .node polygon,#mermaid-svg-MinX7mXw9OTdHHH4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MinX7mXw9OTdHHH4 .rough-node .label text,#mermaid-svg-MinX7mXw9OTdHHH4 .node .label text,#mermaid-svg-MinX7mXw9OTdHHH4 .image-shape .label,#mermaid-svg-MinX7mXw9OTdHHH4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-MinX7mXw9OTdHHH4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MinX7mXw9OTdHHH4 .rough-node .label,#mermaid-svg-MinX7mXw9OTdHHH4 .node .label,#mermaid-svg-MinX7mXw9OTdHHH4 .image-shape .label,#mermaid-svg-MinX7mXw9OTdHHH4 .icon-shape .label{text-align:center;}#mermaid-svg-MinX7mXw9OTdHHH4 .node.clickable{cursor:pointer;}#mermaid-svg-MinX7mXw9OTdHHH4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MinX7mXw9OTdHHH4 .arrowheadPath{fill:#333333;}#mermaid-svg-MinX7mXw9OTdHHH4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MinX7mXw9OTdHHH4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MinX7mXw9OTdHHH4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MinX7mXw9OTdHHH4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MinX7mXw9OTdHHH4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MinX7mXw9OTdHHH4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MinX7mXw9OTdHHH4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MinX7mXw9OTdHHH4 .cluster text{fill:#333;}#mermaid-svg-MinX7mXw9OTdHHH4 .cluster span{color:#333;}#mermaid-svg-MinX7mXw9OTdHHH4 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-MinX7mXw9OTdHHH4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MinX7mXw9OTdHHH4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-MinX7mXw9OTdHHH4 .icon-shape,#mermaid-svg-MinX7mXw9OTdHHH4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MinX7mXw9OTdHHH4 .icon-shape p,#mermaid-svg-MinX7mXw9OTdHHH4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MinX7mXw9OTdHHH4 .icon-shape .label rect,#mermaid-svg-MinX7mXw9OTdHHH4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MinX7mXw9OTdHHH4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MinX7mXw9OTdHHH4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MinX7mXw9OTdHHH4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 标记-清除在老年代的死穴
寻址失败
存活对象 (2MB)
碎片空闲 (1MB)
存活对象 (3MB)
碎片空闲 (2MB)
存活对象 (4MB)
新来一个 10MB 大数组对象
触发 Full GC 或直接抛出 OOM
总空闲内存 500MB,但最大连续单块仅 2MB

这种"总内存明明够,但就是放不下"的假贫穷现象,会频繁逼迫 JVM 发起全堆的 Full GC,甚至直接把服务打挂。

既不能浪费空间,又绝不能容忍碎片。在这样的夹缝中,标记-整理算法(Mark-Compact)应运而生。

二、标记整理的三步走流程

1974 年,计算机科学家 Edward L. Lias 提出了标记-整理算法。

它的核心理念其实非常朴素:既然不能开辟第二块内存来搬家,那我就在当前这一块内存里原地整理。

整个算法的运行过程主要可以拆分为三个步骤:
#mermaid-svg-U5rAw86NjLQWd1Vj{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-U5rAw86NjLQWd1Vj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-U5rAw86NjLQWd1Vj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-U5rAw86NjLQWd1Vj .error-icon{fill:#552222;}#mermaid-svg-U5rAw86NjLQWd1Vj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-U5rAw86NjLQWd1Vj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-U5rAw86NjLQWd1Vj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-U5rAw86NjLQWd1Vj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-U5rAw86NjLQWd1Vj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-U5rAw86NjLQWd1Vj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-U5rAw86NjLQWd1Vj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-U5rAw86NjLQWd1Vj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-U5rAw86NjLQWd1Vj .marker.cross{stroke:#333333;}#mermaid-svg-U5rAw86NjLQWd1Vj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-U5rAw86NjLQWd1Vj p{margin:0;}#mermaid-svg-U5rAw86NjLQWd1Vj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-U5rAw86NjLQWd1Vj .cluster-label text{fill:#333;}#mermaid-svg-U5rAw86NjLQWd1Vj .cluster-label span{color:#333;}#mermaid-svg-U5rAw86NjLQWd1Vj .cluster-label span p{background-color:transparent;}#mermaid-svg-U5rAw86NjLQWd1Vj .label text,#mermaid-svg-U5rAw86NjLQWd1Vj span{fill:#333;color:#333;}#mermaid-svg-U5rAw86NjLQWd1Vj .node rect,#mermaid-svg-U5rAw86NjLQWd1Vj .node circle,#mermaid-svg-U5rAw86NjLQWd1Vj .node ellipse,#mermaid-svg-U5rAw86NjLQWd1Vj .node polygon,#mermaid-svg-U5rAw86NjLQWd1Vj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-U5rAw86NjLQWd1Vj .rough-node .label text,#mermaid-svg-U5rAw86NjLQWd1Vj .node .label text,#mermaid-svg-U5rAw86NjLQWd1Vj .image-shape .label,#mermaid-svg-U5rAw86NjLQWd1Vj .icon-shape .label{text-anchor:middle;}#mermaid-svg-U5rAw86NjLQWd1Vj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-U5rAw86NjLQWd1Vj .rough-node .label,#mermaid-svg-U5rAw86NjLQWd1Vj .node .label,#mermaid-svg-U5rAw86NjLQWd1Vj .image-shape .label,#mermaid-svg-U5rAw86NjLQWd1Vj .icon-shape .label{text-align:center;}#mermaid-svg-U5rAw86NjLQWd1Vj .node.clickable{cursor:pointer;}#mermaid-svg-U5rAw86NjLQWd1Vj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-U5rAw86NjLQWd1Vj .arrowheadPath{fill:#333333;}#mermaid-svg-U5rAw86NjLQWd1Vj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-U5rAw86NjLQWd1Vj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-U5rAw86NjLQWd1Vj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U5rAw86NjLQWd1Vj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-U5rAw86NjLQWd1Vj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U5rAw86NjLQWd1Vj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-U5rAw86NjLQWd1Vj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-U5rAw86NjLQWd1Vj .cluster text{fill:#333;}#mermaid-svg-U5rAw86NjLQWd1Vj .cluster span{color:#333;}#mermaid-svg-U5rAw86NjLQWd1Vj 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-U5rAw86NjLQWd1Vj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-U5rAw86NjLQWd1Vj rect.text{fill:none;stroke-width:0;}#mermaid-svg-U5rAw86NjLQWd1Vj .icon-shape,#mermaid-svg-U5rAw86NjLQWd1Vj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U5rAw86NjLQWd1Vj .icon-shape p,#mermaid-svg-U5rAw86NjLQWd1Vj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-U5rAw86NjLQWd1Vj .icon-shape .label rect,#mermaid-svg-U5rAw86NjLQWd1Vj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U5rAw86NjLQWd1Vj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-U5rAw86NjLQWd1Vj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-U5rAw86NjLQWd1Vj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 顺着 GC Roots 遍历对象图
沿内存单向遍历
根据紧凑边界划线
阶段一:标记 (Mark)
把所有可达的存活对象打上标记
阶段二:整理 (Compact)
将所有存活对象依次压向内存的一端紧凑排列
阶段三:清理 (Sweep)
直接将存活边界之外的所有垃圾内存整体擦除

2.1 顺着根节点找出存活对象

第一步和标记-清除算法完全一样。

当垃圾收集器介入后,首先触发 STW,以一组 GC Roots(比如线程栈帧里的局部变量、类静态变量等)为起点,顺着对象的引用链条向下深搜或广搜。

所有只要在链条上的对象,说明程序后面还要用,就会在对象头的标记字(Mark Word)中打上存活标记。凡是链条摸不到的孤立对象,就是这次要清理的垃圾。

2.2 租房单间里的大扫除

第二步是标记-整理算法最关键的一步,也是它与复制算法最大的分水岭。

想象一下刚毕业在外面租房子的场景:

很多同学刚毕业时住的是开间或者单间,屋里只有一间房。这就像标记-整理算法面对的内存环境。

如果是两室一厅,你打扫卫生可以学复制算法------把所有的衣服、书本一口气搬到隔壁空房间,把原房间地板彻底拖干净,再住进去。

但如果你手头就只有一个单间,你不可能把家当扔到门外楼道里。你唯一的办法,就是把散落在一地的有用物品一件件捡起来,沿着房间的某一面墙,整整齐齐地码在角落里。

在 JVM 内存里也是一模一样:
#mermaid-svg-3ZDqVIZsgaFQg3Os{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-3ZDqVIZsgaFQg3Os .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3ZDqVIZsgaFQg3Os .error-icon{fill:#552222;}#mermaid-svg-3ZDqVIZsgaFQg3Os .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3ZDqVIZsgaFQg3Os .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3ZDqVIZsgaFQg3Os .marker.cross{stroke:#333333;}#mermaid-svg-3ZDqVIZsgaFQg3Os svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3ZDqVIZsgaFQg3Os p{margin:0;}#mermaid-svg-3ZDqVIZsgaFQg3Os .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3ZDqVIZsgaFQg3Os .cluster-label text{fill:#333;}#mermaid-svg-3ZDqVIZsgaFQg3Os .cluster-label span{color:#333;}#mermaid-svg-3ZDqVIZsgaFQg3Os .cluster-label span p{background-color:transparent;}#mermaid-svg-3ZDqVIZsgaFQg3Os .label text,#mermaid-svg-3ZDqVIZsgaFQg3Os span{fill:#333;color:#333;}#mermaid-svg-3ZDqVIZsgaFQg3Os .node rect,#mermaid-svg-3ZDqVIZsgaFQg3Os .node circle,#mermaid-svg-3ZDqVIZsgaFQg3Os .node ellipse,#mermaid-svg-3ZDqVIZsgaFQg3Os .node polygon,#mermaid-svg-3ZDqVIZsgaFQg3Os .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3ZDqVIZsgaFQg3Os .rough-node .label text,#mermaid-svg-3ZDqVIZsgaFQg3Os .node .label text,#mermaid-svg-3ZDqVIZsgaFQg3Os .image-shape .label,#mermaid-svg-3ZDqVIZsgaFQg3Os .icon-shape .label{text-anchor:middle;}#mermaid-svg-3ZDqVIZsgaFQg3Os .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3ZDqVIZsgaFQg3Os .rough-node .label,#mermaid-svg-3ZDqVIZsgaFQg3Os .node .label,#mermaid-svg-3ZDqVIZsgaFQg3Os .image-shape .label,#mermaid-svg-3ZDqVIZsgaFQg3Os .icon-shape .label{text-align:center;}#mermaid-svg-3ZDqVIZsgaFQg3Os .node.clickable{cursor:pointer;}#mermaid-svg-3ZDqVIZsgaFQg3Os .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3ZDqVIZsgaFQg3Os .arrowheadPath{fill:#333333;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3ZDqVIZsgaFQg3Os .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3ZDqVIZsgaFQg3Os .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3ZDqVIZsgaFQg3Os .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3ZDqVIZsgaFQg3Os .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3ZDqVIZsgaFQg3Os .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3ZDqVIZsgaFQg3Os .cluster text{fill:#333;}#mermaid-svg-3ZDqVIZsgaFQg3Os .cluster span{color:#333;}#mermaid-svg-3ZDqVIZsgaFQg3Os 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-3ZDqVIZsgaFQg3Os .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3ZDqVIZsgaFQg3Os rect.text{fill:none;stroke-width:0;}#mermaid-svg-3ZDqVIZsgaFQg3Os .icon-shape,#mermaid-svg-3ZDqVIZsgaFQg3Os .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3ZDqVIZsgaFQg3Os .icon-shape p,#mermaid-svg-3ZDqVIZsgaFQg3Os .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3ZDqVIZsgaFQg3Os .icon-shape .label rect,#mermaid-svg-3ZDqVIZsgaFQg3Os .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3ZDqVIZsgaFQg3Os .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3ZDqVIZsgaFQg3Os .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3ZDqVIZsgaFQg3Os :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 整理后的紧凑内存
存活 A
存活 B (向前平移)
存活 C (向前平移)
【存活边界】
完全连续的空闲内存空间
整理前的混乱内存
存活 A
垃圾 1
存活 B
垃圾 2
存活 C
垃圾 3

GC 线程开始线性扫描整块内存,从内存的起始地址(低地址端)向高地址端推进:

  1. 遇到第一个存活对象 A,发现它已经在最前端,不用动它;
  2. 继续往后扫,跳过垃圾 1,找到了第二个存活对象 B;
  3. 将对象 B 的整块内存数据,向前复制挪动到紧挨着对象 A 的屁股后面;
  4. 继续跳过垃圾 2,找到存活对象 C,再把对象 C 平移挪到对象 B 后面。

一路推下来,所有的存活对象都被规整地排在了内存的一端,原本夹在它们中间的所有空隙全被挤压掉了。

2.3 存活边界划定后的一键清理

当最后一个存活对象移动到位后,一个非常清晰的**存活对象边界(Boundary)**就自然浮现出来了。

在边界的左侧(低地址侧),全都是紧凑排列的活对象,没有留下一丝一毫的碎片缝隙;

在边界的右侧(高地址侧),原先残留的所有垃圾对象以及平移前留下的无用数据,统统属于死物。

这时候第三步就非常简单了:JVM 根本不需要一个个去抹除垃圾对象的信息,它只需要拿到边界指针,把边界之外的所有内存区域直接标记为空闲可用,或者直接将空闲指针重置。

清理完成后,后续新对象在老年代分配时,又可以像新生代那样享受极其高效的**指针碰撞(Bump the Pointer)**分配了。

三、为什么大家都在说它慢

看完前面的流程,你可能会觉得标记-整理算法简直是集大成者:

既不像标记-清除那样搞出一地碎片,又不像复制算法那样平白无故吃掉一半内存。

但工业界没有免费的午餐。标记-整理算法之所以被很多开发者乃至 JVM 设计者视为"性能杀手",核心就在于它第二步做出的巨大牺牲。

它到底慢在哪里?主要有三个无法绕开的成本。

3.1 内存里的物理搬家开销

在标记-清除算法里,存活对象自始至终待在原地不动,GC 只需要在内存里擦掉死对象的记录。

而在标记-整理算法里,每一个存活对象都要在内存里经历一次物理拷贝。

如果一个对象占了 1 MB 内存,JVM 就要实打实地在内存总线上搬运 1 MB 的数据;如果老年代里有 3 GB 的存活对象,GC 整理一轮就意味着要在内存里来回倒腾几吉字节的数据。这对 CPU 缓存行(Cache Line)和内存总线带宽会带来相当沉重的负担。

3.2 真正的大坑是指针修正

如果只是搬运数据,现代 CPU 的内存拷贝吞吐量还不至于让 GC 停顿几十秒甚至几分钟。

标记-整理算法最隐蔽、也是耗时占比最高的操作,其实是引用的重定位(Reference Fixing / Update References)。

在 Java 虚拟机里,业务代码是如何找到一个对象的?是通过保存在各个地方的内存地址指针(reference)。

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

public class MemoryReferenceDemo {
    // 静态变量保存在方法区,持有堆中对象的物理引用
    private static ServiceManager globalManager;

    public void processTask() {
        // 局部变量保存在线程栈帧的局部变量表,持有堆中对象的物理引用
        TaskWorker localWorker = new TaskWorker();
        localWorker.doWork();
    }
}

请仔细想想:当 GC 线程把堆里的对象从内存地址 0x1000 搬移到了 0x0200 时,系统里到底发生了什么?
GC 目标对象 (正在搬家) 堆内其他存活对象 方法区 (静态变量引用) 线程栈 (局部变量表) GC 目标对象 (正在搬家) 堆内其他存活对象 方法区 (静态变量引用) 线程栈 (局部变量表) #mermaid-svg-cBlwmpwlB9VPCqms{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-cBlwmpwlB9VPCqms .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cBlwmpwlB9VPCqms .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cBlwmpwlB9VPCqms .error-icon{fill:#552222;}#mermaid-svg-cBlwmpwlB9VPCqms .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cBlwmpwlB9VPCqms .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cBlwmpwlB9VPCqms .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cBlwmpwlB9VPCqms .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cBlwmpwlB9VPCqms .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cBlwmpwlB9VPCqms .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cBlwmpwlB9VPCqms .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cBlwmpwlB9VPCqms .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cBlwmpwlB9VPCqms .marker.cross{stroke:#333333;}#mermaid-svg-cBlwmpwlB9VPCqms svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cBlwmpwlB9VPCqms p{margin:0;}#mermaid-svg-cBlwmpwlB9VPCqms .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cBlwmpwlB9VPCqms text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-cBlwmpwlB9VPCqms .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cBlwmpwlB9VPCqms .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-cBlwmpwlB9VPCqms .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-cBlwmpwlB9VPCqms .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-cBlwmpwlB9VPCqms #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-cBlwmpwlB9VPCqms .sequenceNumber{fill:white;}#mermaid-svg-cBlwmpwlB9VPCqms #sequencenumber{fill:#333;}#mermaid-svg-cBlwmpwlB9VPCqms #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-cBlwmpwlB9VPCqms .messageText{fill:#333;stroke:none;}#mermaid-svg-cBlwmpwlB9VPCqms .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cBlwmpwlB9VPCqms .labelText,#mermaid-svg-cBlwmpwlB9VPCqms .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-cBlwmpwlB9VPCqms .loopText,#mermaid-svg-cBlwmpwlB9VPCqms .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-cBlwmpwlB9VPCqms .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-cBlwmpwlB9VPCqms .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-cBlwmpwlB9VPCqms .noteText,#mermaid-svg-cBlwmpwlB9VPCqms .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-cBlwmpwlB9VPCqms .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cBlwmpwlB9VPCqms .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cBlwmpwlB9VPCqms .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cBlwmpwlB9VPCqms .actorPopupMenu{position:absolute;}#mermaid-svg-cBlwmpwlB9VPCqms .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-cBlwmpwlB9VPCqms .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cBlwmpwlB9VPCqms .actor-man circle,#mermaid-svg-cBlwmpwlB9VPCqms line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-cBlwmpwlB9VPCqms :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原物理内存地址: 0x1000 栈帧局部变量指针指向 0x1000 静态引用指针指向 0x1000 内部成员属性指针指向 0x1000 对象搬家完成,原地址 0x1000 作废! 核心耗时阶段:全局指针修正 执行数据平移,挪动到新地址 0x0200 1 扫描所有线程栈帧,将局部变量指针更新为 0x0200 2 扫描方法区,将静态变量指针更新为 0x0200 3 遍历堆中所有存活对象的字段,凡是引用该对象的指针全部重写为 0x0200 4

对象搬家不是改它自己一个人的事情。只要它的物理地址变了,所有引用它的地方必须全部跟着修改:

  1. 每一个正在运行的 Java 线程栈里,局部变量表中保存的指针必须更新;
  2. 方法区(元空间)里所有持有该引用的静态变量必须更新;
  3. 堆内存中所有其他存活对象,如果内部有字段指向了这个被移动的对象,它们的字段指针也必须更新!

这一步要对整个运行时数据区做极度严密的地毯式搜索和逐一重写。堆越大、存活对象越多、对象之间的网状引用关系越复杂,指针修正花费的时间就越恐怖。

3.3 业务线程必须被死死卡住

更残酷的是:在对象搬家和修改指针的整个过程中,必须全程停掉所有业务线程(Stop The World)。

试想一下:如果 GC 正在把一个对象从 0x1000 搬到 0x0200,指针还没来得及改完,你的业务线程突然读了栈帧里的旧指针去访问 0x1000,这块内存可能已经被重置或者被别的对象占领了。这就是严重的悬空指针(Dangling Pointer)和数据错乱,甚至会直接导致操作系统层面的 JVM 崩溃(Crash)。

为了绝对的内存安全,JVM 只能把所有业务线程死死定在安全点(Safepoint)上,等到所有对象搬完、所有引用改完,才敢放行。

这就是为什么基于标记-整理算法实现的收集器(比如 Serial Old、Parallel Old),一旦触发 Full GC,停顿时间动辄几秒甚至几十秒。对于高并发低延迟的在线服务来说,这种长停顿简直就是灾难。

四、三大经典算法的取舍

学到这里,JVM 垃圾收集领域的"三大基础运算法"我们就全部聚齐了:

  1. 标记-清除(Mark-Sweep)
  2. 复制算法(Copying)
  3. 标记-整理(Mark-Compact)

计算机科学里从来没有银弹。这三个算法没有谁能全面碾压谁,它们恰好构成了一个经典的性能与空间不可能三角:

对比维度 标记-清除(Mark-Sweep) 复制算法(Copying) 标记-整理(Mark-Compact)
执行速度 中等(只遍历垃圾,不移对象) 最快(只遍历存活对象,单次拷贝) 最慢(标记 + 移动 + 全局改指针)
内存利用率 100%(全部空间均可分配) 50%(需要预留另一半或 Survivor) 100%(单块内存紧凑利用)
内存碎片 严重(散落大量空洞,大对象难塞) 无碎片(连续规整存储) 无碎片(连续规整存储)
分配机制 空闲链表(查找匹配慢) 指针碰撞(两行指令极速分配) 指针碰撞(两行指令极速分配)
指针移动成本 无(对象原地不动) 较重(但新生代存活对象极少) 极重(存活对象多且全局指针修正)

#mermaid-svg-MlvCP5eIkf6qolOB{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-MlvCP5eIkf6qolOB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MlvCP5eIkf6qolOB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MlvCP5eIkf6qolOB .error-icon{fill:#552222;}#mermaid-svg-MlvCP5eIkf6qolOB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MlvCP5eIkf6qolOB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MlvCP5eIkf6qolOB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MlvCP5eIkf6qolOB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MlvCP5eIkf6qolOB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MlvCP5eIkf6qolOB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MlvCP5eIkf6qolOB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MlvCP5eIkf6qolOB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MlvCP5eIkf6qolOB .marker.cross{stroke:#333333;}#mermaid-svg-MlvCP5eIkf6qolOB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MlvCP5eIkf6qolOB p{margin:0;}#mermaid-svg-MlvCP5eIkf6qolOB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MlvCP5eIkf6qolOB .cluster-label text{fill:#333;}#mermaid-svg-MlvCP5eIkf6qolOB .cluster-label span{color:#333;}#mermaid-svg-MlvCP5eIkf6qolOB .cluster-label span p{background-color:transparent;}#mermaid-svg-MlvCP5eIkf6qolOB .label text,#mermaid-svg-MlvCP5eIkf6qolOB span{fill:#333;color:#333;}#mermaid-svg-MlvCP5eIkf6qolOB .node rect,#mermaid-svg-MlvCP5eIkf6qolOB .node circle,#mermaid-svg-MlvCP5eIkf6qolOB .node ellipse,#mermaid-svg-MlvCP5eIkf6qolOB .node polygon,#mermaid-svg-MlvCP5eIkf6qolOB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MlvCP5eIkf6qolOB .rough-node .label text,#mermaid-svg-MlvCP5eIkf6qolOB .node .label text,#mermaid-svg-MlvCP5eIkf6qolOB .image-shape .label,#mermaid-svg-MlvCP5eIkf6qolOB .icon-shape .label{text-anchor:middle;}#mermaid-svg-MlvCP5eIkf6qolOB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MlvCP5eIkf6qolOB .rough-node .label,#mermaid-svg-MlvCP5eIkf6qolOB .node .label,#mermaid-svg-MlvCP5eIkf6qolOB .image-shape .label,#mermaid-svg-MlvCP5eIkf6qolOB .icon-shape .label{text-align:center;}#mermaid-svg-MlvCP5eIkf6qolOB .node.clickable{cursor:pointer;}#mermaid-svg-MlvCP5eIkf6qolOB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MlvCP5eIkf6qolOB .arrowheadPath{fill:#333333;}#mermaid-svg-MlvCP5eIkf6qolOB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MlvCP5eIkf6qolOB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MlvCP5eIkf6qolOB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MlvCP5eIkf6qolOB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MlvCP5eIkf6qolOB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MlvCP5eIkf6qolOB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MlvCP5eIkf6qolOB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MlvCP5eIkf6qolOB .cluster text{fill:#333;}#mermaid-svg-MlvCP5eIkf6qolOB .cluster span{color:#333;}#mermaid-svg-MlvCP5eIkf6qolOB 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-MlvCP5eIkf6qolOB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MlvCP5eIkf6qolOB rect.text{fill:none;stroke-width:0;}#mermaid-svg-MlvCP5eIkf6qolOB .icon-shape,#mermaid-svg-MlvCP5eIkf6qolOB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MlvCP5eIkf6qolOB .icon-shape p,#mermaid-svg-MlvCP5eIkf6qolOB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MlvCP5eIkf6qolOB .icon-shape .label rect,#mermaid-svg-MlvCP5eIkf6qolOB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MlvCP5eIkf6qolOB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MlvCP5eIkf6qolOB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MlvCP5eIkf6qolOB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 对象特点:存活率极低 90%以上朝生夕死
对象特点:存活率极高 且无备用担保
要极致低停顿 容忍碎片
要高吞吐 彻底杜绝碎片
碎片堆满无法分配大对象时
堆内存分代收集的智慧
新生代 (Young Gen)
老年代 (Old Gen)
首选:复制算法 (Copying)
只搬少量幸存者,速度极快

通过 Eden + 2个Survivor 将浪费降到 10%
老年代的取舍
阶段性使用:标记-清除 (如 CMS)
保底兜底:标记-整理 (如 Parallel Old)
退化触发 Serial Old 标记-整理做彻底压缩

4.1 速度与空间的三角平衡

看看上面的对照表,你会发现设计一个垃圾收集器就像在玩天平:

  • 如果你想要最极致的速度 ,且能接受一定的空间折损,而且对象死得很快,那复制算法是当之无愧的王者;
  • 如果你一分钱内存都不想多花 ,也暂时不想承受搬家的巨大停顿,那标记-清除是最省事的折中,但你得时刻防备碎片带来的暴击;
  • 如果你既想要 100% 的空间利用率,又必须彻底干掉碎片,那你就必须做好准备,用长时间的 STW 和高昂的 CPU 开销去换取整洁的内存。

4.2 分代收集给出的折中答案

正是因为没有任何单一算法能够一统天下,JVM 的设计者才提出了分代收集理论(Generational Collection)。

把堆内存按照对象的生命周期拆成新生代和老年代,让复制算法去守新生代,发挥它面对大量死亡对象时的极速收割能力;

让标记-整理或者标记-清除去守老年代,处理那些顽固、体积庞大且不容浪费空间的存活对象。

甚至连号称低停顿的 CMS 收集器,虽然平时在老年代采用标记-清除来减少暂停时间,但也专门配置了 -XX:+UseCMSCompactAtFullCollection 参数------平时容忍碎片,一旦碎片多到连一个大对象都塞不下了,立马退化调用标记-整理算法来做一次彻头彻尾的内存大压缩。

写在最后

标记-整理算法的思路其实一点都不复杂,核心就是三个动作:标记存活对象、往内存一端码整齐、抹掉边界外的垃圾。

它用看似简单的搬家动作,彻底拔掉了内存碎片的肉中刺,但背后付出的代价是极其沉重的物理拷贝、指针重写以及漫长的 STW 停顿。在很多技术面试中,面试官问你为什么老年代 GC 停顿那么长,答出"标记整理需要全量更新引用指针和挂起线程",往往比干巴巴背出优缺点三个字深刻得多。

关于 JVM 垃圾收集的经典算法,到这里我们就全部盘点完毕了。后面我们会直接走进经典垃圾收集器(Serial、Parallel、CMS、G1)的真实战场,看看工业级框架是如何在这些理论算法上做出极致工程调优的。如果你觉得这篇内容讲得透彻,记得点个关注,咱们下一期继续死磕 JVM 底层!

相关推荐
让学习成为一种生活方式1 小时前
PlantCellChat--单细胞与空间转录组数据预测植物细胞间通讯的R包--The Plant Journal
算法
C++ 老炮儿的技术栈1 小时前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c
个 人 练 习 生1 小时前
C++:模板初阶详解
开发语言·c++·经验分享·学习
辣知2 小时前
辣知·化智69 西周青铜器何尊的宅兹中国
学习
梦帮科技2 小时前
多任务权重流形融合:SLERP 球形线性插值、DARE 稀疏剪枝与多专家模型融合落地
人工智能·深度学习·算法·机器学习·tensorflow·聚类·剪枝
Tisfy2 小时前
LeetCode 0020.有效的括号:栈匹配
算法·leetcode·字符串·题解·括号匹配
AOI小白新手上路2 小时前
韦东山 3-5 I.MX6ULL 的 LED 编程 · 学习笔记
arm开发·笔记·单片机·学习·架构·硬件架构
知识分享小能手2 小时前
C++ 学习教程,从入门到精通,C++类继承 — 完整知识点详解(13)
开发语言·c++·学习
测试学习test2 小时前
SPI读取W25Q64硬件驱动(HAL库)
数据库·stm32·嵌入式硬件·算法