前言
上一篇聊完复制算法,很多同学会觉得它虽然效率极高,但浪费一部分内存的代价还是太奢侈了。面对存活率极高、动辄几个 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% 以上。如果老年代也用复制算法,会遇到两个致命死穴:
- 空间浪费无法承受:如果老年代有 4 GB 内存,为了做半区复制,你就得拿出 2 GB 常年空着不用。对生产环境寸土寸金的堆内存来说,这纯粹是在挥霍资源。
- 没有其他区域能做分配担保 :新生代之所以敢只留一小块 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 线程开始线性扫描整块内存,从内存的起始地址(低地址端)向高地址端推进:
- 遇到第一个存活对象 A,发现它已经在最前端,不用动它;
- 继续往后扫,跳过垃圾 1,找到了第二个存活对象 B;
- 将对象 B 的整块内存数据,向前复制挪动到紧挨着对象 A 的屁股后面;
- 继续跳过垃圾 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
对象搬家不是改它自己一个人的事情。只要它的物理地址变了,所有引用它的地方必须全部跟着修改:
- 每一个正在运行的 Java 线程栈里,局部变量表中保存的指针必须更新;
- 方法区(元空间)里所有持有该引用的静态变量必须更新;
- 堆内存中所有其他存活对象,如果内部有字段指向了这个被移动的对象,它们的字段指针也必须更新!
这一步要对整个运行时数据区做极度严密的地毯式搜索和逐一重写。堆越大、存活对象越多、对象之间的网状引用关系越复杂,指针修正花费的时间就越恐怖。
3.3 业务线程必须被死死卡住
更残酷的是:在对象搬家和修改指针的整个过程中,必须全程停掉所有业务线程(Stop The World)。
试想一下:如果 GC 正在把一个对象从 0x1000 搬到 0x0200,指针还没来得及改完,你的业务线程突然读了栈帧里的旧指针去访问 0x1000,这块内存可能已经被重置或者被别的对象占领了。这就是严重的悬空指针(Dangling Pointer)和数据错乱,甚至会直接导致操作系统层面的 JVM 崩溃(Crash)。
为了绝对的内存安全,JVM 只能把所有业务线程死死定在安全点(Safepoint)上,等到所有对象搬完、所有引用改完,才敢放行。
这就是为什么基于标记-整理算法实现的收集器(比如 Serial Old、Parallel Old),一旦触发 Full GC,停顿时间动辄几秒甚至几十秒。对于高并发低延迟的在线服务来说,这种长停顿简直就是灾难。
四、三大经典算法的取舍
学到这里,JVM 垃圾收集领域的"三大基础运算法"我们就全部聚齐了:
- 标记-清除(Mark-Sweep)
- 复制算法(Copying)
- 标记-整理(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 底层!