JVM 学习文档(全貌通览)
版本基准 :OpenJDK 21.0.11 LTS(HotSpot 64-Bit Server VM),class file 主版本号 65
实测环境 :Windows / 20 核 / 32GB 内存,文中所有「实测输出」均在本机该版本下真实跑出
学习方式 :每个知识点按「概念 → 图示 → 可运行代码 → 实战陷阱」四段式组织
配套代码 :
jvm-demo/目录(另附jvm-demo.zip),13 个 Demo 全部编译运行通过图示说明:文中图形为 Mermaid 代码块,在 Typora / VS Code / GitHub 等支持 Mermaid 的编辑器中可直接渲染
目录
- [JVM 整体架构](#JVM 整体架构)
- 类加载机制
- 运行时数据区
- 垃圾回收
- [JIT 编译与执行引擎](#JIT 编译与执行引擎)
- [线程模型与虚拟线程(JDK 21)](#线程模型与虚拟线程(JDK 21))
- 性能监控与调优
- 附录
1. JVM 整体架构
1.1 从 .java 到运行:一张图
#mermaid-svg-ugMgEvYcxXsOr6xt{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-ugMgEvYcxXsOr6xt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ugMgEvYcxXsOr6xt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ugMgEvYcxXsOr6xt .error-icon{fill:#552222;}#mermaid-svg-ugMgEvYcxXsOr6xt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ugMgEvYcxXsOr6xt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ugMgEvYcxXsOr6xt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ugMgEvYcxXsOr6xt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ugMgEvYcxXsOr6xt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ugMgEvYcxXsOr6xt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ugMgEvYcxXsOr6xt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ugMgEvYcxXsOr6xt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ugMgEvYcxXsOr6xt .marker.cross{stroke:#333333;}#mermaid-svg-ugMgEvYcxXsOr6xt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ugMgEvYcxXsOr6xt p{margin:0;}#mermaid-svg-ugMgEvYcxXsOr6xt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ugMgEvYcxXsOr6xt .cluster-label text{fill:#333;}#mermaid-svg-ugMgEvYcxXsOr6xt .cluster-label span{color:#333;}#mermaid-svg-ugMgEvYcxXsOr6xt .cluster-label span p{background-color:transparent;}#mermaid-svg-ugMgEvYcxXsOr6xt .label text,#mermaid-svg-ugMgEvYcxXsOr6xt span{fill:#333;color:#333;}#mermaid-svg-ugMgEvYcxXsOr6xt .node rect,#mermaid-svg-ugMgEvYcxXsOr6xt .node circle,#mermaid-svg-ugMgEvYcxXsOr6xt .node ellipse,#mermaid-svg-ugMgEvYcxXsOr6xt .node polygon,#mermaid-svg-ugMgEvYcxXsOr6xt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ugMgEvYcxXsOr6xt .rough-node .label text,#mermaid-svg-ugMgEvYcxXsOr6xt .node .label text,#mermaid-svg-ugMgEvYcxXsOr6xt .image-shape .label,#mermaid-svg-ugMgEvYcxXsOr6xt .icon-shape .label{text-anchor:middle;}#mermaid-svg-ugMgEvYcxXsOr6xt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ugMgEvYcxXsOr6xt .rough-node .label,#mermaid-svg-ugMgEvYcxXsOr6xt .node .label,#mermaid-svg-ugMgEvYcxXsOr6xt .image-shape .label,#mermaid-svg-ugMgEvYcxXsOr6xt .icon-shape .label{text-align:center;}#mermaid-svg-ugMgEvYcxXsOr6xt .node.clickable{cursor:pointer;}#mermaid-svg-ugMgEvYcxXsOr6xt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ugMgEvYcxXsOr6xt .arrowheadPath{fill:#333333;}#mermaid-svg-ugMgEvYcxXsOr6xt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ugMgEvYcxXsOr6xt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ugMgEvYcxXsOr6xt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ugMgEvYcxXsOr6xt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ugMgEvYcxXsOr6xt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ugMgEvYcxXsOr6xt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ugMgEvYcxXsOr6xt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ugMgEvYcxXsOr6xt .cluster text{fill:#333;}#mermaid-svg-ugMgEvYcxXsOr6xt .cluster span{color:#333;}#mermaid-svg-ugMgEvYcxXsOr6xt 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-ugMgEvYcxXsOr6xt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ugMgEvYcxXsOr6xt rect.text{fill:none;stroke-width:0;}#mermaid-svg-ugMgEvYcxXsOr6xt .icon-shape,#mermaid-svg-ugMgEvYcxXsOr6xt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ugMgEvYcxXsOr6xt .icon-shape p,#mermaid-svg-ugMgEvYcxXsOr6xt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ugMgEvYcxXsOr6xt .icon-shape .label rect,#mermaid-svg-ugMgEvYcxXsOr6xt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ugMgEvYcxXsOr6xt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ugMgEvYcxXsOr6xt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ugMgEvYcxXsOr6xt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} javac 编译
类加载
Java 源码 .java
字节码 .class
JVM
类加载子系统
运行时数据区
执行引擎
本地方法接口 JNI
PC 寄存器
虚拟机栈 / 本地方法栈
堆
方法区(元空间)
解释器 Interpreter
JIT 编译器 C1 / C2
垃圾回收器 GC
1.2 四大子系统职责
| 子系统 | 职责 | 关键点 |
|---|---|---|
| 类加载子系统 | 把 .class 加载进内存,完成校验、准备、解析、初始化 | 双亲委派、自定义加载器 |
| 运行时数据区 | 存放类信息、对象、方法执行上下文 | 线程私有 vs 全局共享 |
| 执行引擎 | 执行字节码:解释执行 + JIT 编译 + GC | 分层编译、热点探测 |
| 本地方法接口(JNI) | 调用 C/C++ 本地库 | 本地方法栈为其服务 |
1.3 "一次编译,到处运行"的真相
- 跨平台的是字节码,不是 Java 源码,也不是 JVM 行为本身。
- 字节码被 JIT 编译成本地机器码后,产物与操作系统、CPU 架构绑定。
- 不同 JVM 实现对同一份字节码的执行效果(GC 行为、JIT 优化程度)可能有差异,所以线上要固定 JDK 版本与厂商。
⚠️ 实战陷阱 :
System.getProperty("java.version")拿到的是运行时版本,编译期用的是javac版本。用 JDK 21 编译(class file 65)的类不能跑在 JDK 8 上,会抛UnsupportedClassVersionError。CI 里 compile 与 runtime 的 JDK 必须对齐。
2. 类加载机制
2.1 类的生命周期:五阶段
#mermaid-svg-i5B8ZzP0Pl8DS2TW{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-i5B8ZzP0Pl8DS2TW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .error-icon{fill:#552222;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .marker.cross{stroke:#333333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-i5B8ZzP0Pl8DS2TW p{margin:0;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .cluster-label text{fill:#333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .cluster-label span{color:#333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .cluster-label span p{background-color:transparent;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .label text,#mermaid-svg-i5B8ZzP0Pl8DS2TW span{fill:#333;color:#333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .node rect,#mermaid-svg-i5B8ZzP0Pl8DS2TW .node circle,#mermaid-svg-i5B8ZzP0Pl8DS2TW .node ellipse,#mermaid-svg-i5B8ZzP0Pl8DS2TW .node polygon,#mermaid-svg-i5B8ZzP0Pl8DS2TW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .rough-node .label text,#mermaid-svg-i5B8ZzP0Pl8DS2TW .node .label text,#mermaid-svg-i5B8ZzP0Pl8DS2TW .image-shape .label,#mermaid-svg-i5B8ZzP0Pl8DS2TW .icon-shape .label{text-anchor:middle;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .rough-node .label,#mermaid-svg-i5B8ZzP0Pl8DS2TW .node .label,#mermaid-svg-i5B8ZzP0Pl8DS2TW .image-shape .label,#mermaid-svg-i5B8ZzP0Pl8DS2TW .icon-shape .label{text-align:center;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .node.clickable{cursor:pointer;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .arrowheadPath{fill:#333333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-i5B8ZzP0Pl8DS2TW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i5B8ZzP0Pl8DS2TW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-i5B8ZzP0Pl8DS2TW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .cluster text{fill:#333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .cluster span{color:#333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW 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-i5B8ZzP0Pl8DS2TW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-i5B8ZzP0Pl8DS2TW rect.text{fill:none;stroke-width:0;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .icon-shape,#mermaid-svg-i5B8ZzP0Pl8DS2TW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .icon-shape p,#mermaid-svg-i5B8ZzP0Pl8DS2TW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .icon-shape .label rect,#mermaid-svg-i5B8ZzP0Pl8DS2TW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i5B8ZzP0Pl8DS2TW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-i5B8ZzP0Pl8DS2TW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-i5B8ZzP0Pl8DS2TW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 加载 Loading
验证 Verification
准备 Preparation
解析 Resolution
初始化 Initialization
使用 Using
卸载 Unloading
| 阶段 | 做什么 |
|---|---|
| 加载 | 通过全限定名读取字节流,转为方法区结构,生成 java.lang.Class 对象 |
| 验证 | 文件格式、元数据、字节码、符号引用校验(防止恶意字节码) |
| 准备 | 为静态变量 分配内存并赋零值(如 static int x → 0,不是代码里的初始值) |
| 解析 | 符号引用替换为直接引用 |
| 初始化 | 执行静态代码块和静态变量赋值,即 <clinit>() 方法 |
2.2 类加载器与双亲委派
#mermaid-svg-xfUQVrbGclAIAnct{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-xfUQVrbGclAIAnct .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xfUQVrbGclAIAnct .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xfUQVrbGclAIAnct .error-icon{fill:#552222;}#mermaid-svg-xfUQVrbGclAIAnct .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xfUQVrbGclAIAnct .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xfUQVrbGclAIAnct .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xfUQVrbGclAIAnct .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xfUQVrbGclAIAnct .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xfUQVrbGclAIAnct .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xfUQVrbGclAIAnct .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xfUQVrbGclAIAnct .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xfUQVrbGclAIAnct .marker.cross{stroke:#333333;}#mermaid-svg-xfUQVrbGclAIAnct svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xfUQVrbGclAIAnct p{margin:0;}#mermaid-svg-xfUQVrbGclAIAnct .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xfUQVrbGclAIAnct .cluster-label text{fill:#333;}#mermaid-svg-xfUQVrbGclAIAnct .cluster-label span{color:#333;}#mermaid-svg-xfUQVrbGclAIAnct .cluster-label span p{background-color:transparent;}#mermaid-svg-xfUQVrbGclAIAnct .label text,#mermaid-svg-xfUQVrbGclAIAnct span{fill:#333;color:#333;}#mermaid-svg-xfUQVrbGclAIAnct .node rect,#mermaid-svg-xfUQVrbGclAIAnct .node circle,#mermaid-svg-xfUQVrbGclAIAnct .node ellipse,#mermaid-svg-xfUQVrbGclAIAnct .node polygon,#mermaid-svg-xfUQVrbGclAIAnct .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xfUQVrbGclAIAnct .rough-node .label text,#mermaid-svg-xfUQVrbGclAIAnct .node .label text,#mermaid-svg-xfUQVrbGclAIAnct .image-shape .label,#mermaid-svg-xfUQVrbGclAIAnct .icon-shape .label{text-anchor:middle;}#mermaid-svg-xfUQVrbGclAIAnct .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xfUQVrbGclAIAnct .rough-node .label,#mermaid-svg-xfUQVrbGclAIAnct .node .label,#mermaid-svg-xfUQVrbGclAIAnct .image-shape .label,#mermaid-svg-xfUQVrbGclAIAnct .icon-shape .label{text-align:center;}#mermaid-svg-xfUQVrbGclAIAnct .node.clickable{cursor:pointer;}#mermaid-svg-xfUQVrbGclAIAnct .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xfUQVrbGclAIAnct .arrowheadPath{fill:#333333;}#mermaid-svg-xfUQVrbGclAIAnct .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xfUQVrbGclAIAnct .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xfUQVrbGclAIAnct .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xfUQVrbGclAIAnct .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xfUQVrbGclAIAnct .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xfUQVrbGclAIAnct .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xfUQVrbGclAIAnct .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xfUQVrbGclAIAnct .cluster text{fill:#333;}#mermaid-svg-xfUQVrbGclAIAnct .cluster span{color:#333;}#mermaid-svg-xfUQVrbGclAIAnct 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-xfUQVrbGclAIAnct .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xfUQVrbGclAIAnct rect.text{fill:none;stroke-width:0;}#mermaid-svg-xfUQVrbGclAIAnct .icon-shape,#mermaid-svg-xfUQVrbGclAIAnct .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xfUQVrbGclAIAnct .icon-shape p,#mermaid-svg-xfUQVrbGclAIAnct .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xfUQVrbGclAIAnct .icon-shape .label rect,#mermaid-svg-xfUQVrbGclAIAnct .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xfUQVrbGclAIAnct .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xfUQVrbGclAIAnct .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xfUQVrbGclAIAnct :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 向上委派
2. 向上委派
3. 向上委派
都没找到,逐级回退
4. findClass 自己加载
自定义 ClassLoader
AppClassLoader
(类路径)
PlatformClassLoader
(JDK 平台模块)
Bootstrap
(C++ 实现,Java 侧看不到)
加载完成
| 加载器 | 加载范围 | JDK 21 中的样子 |
|---|---|---|
| Bootstrap | java.base 等核心模块 |
Java 侧 getClassLoader() 返回 null |
| Platform | JDK 平台模块(如 java.sql、jdk.*) |
jdk.internal.loader.ClassLoaders$PlatformClassLoader |
| App | 类路径 -classpath / CLASSPATH |
jdk.internal.loader.ClassLoaders$AppClassLoader |
双亲委派模型 :一个类加载器收到加载请求,先委派父加载器,父加载器继续向上,直到 Bootstrap;都找不到才自己加载。好处是保证核心类唯一 ,防止用户自定义一个 java.lang.Object 被加载。
2.3 打破双亲委派的三大场景
- SPI 机制 :
DriverManager由 Bootstrap 加载,MySQL 驱动在 App 层。Bootstrap 反过来需要加载 App 层的类,只能通过Thread.currentThread().getContextClassLoader()(线程上下文类加载器)拿到 App 加载器。 - Tomcat :每个 Web 应用一个类加载器,实现应用间隔离、热部署;
javax.servlet等容器类则向上委派。 - OSGi / 热部署框架:网状委派,程序自己控制加载边界。
2.4 主动引用 vs 被动引用:初始化时机
主动引用(必然触发初始化):
new实例化、读写静态字段(非 final 常量)、调用静态方法;- 反射调用(
Class.forName); - 初始化子类会先触发父类初始化;
- 启动类(
main所在类)。
被动引用(不触发初始化):
- 子类访问父类的静态字段 → 只初始化父类;
- 定义对象数组(
new Child[3]); - 访问编译期常量(
static final基本类型/字符串,已被内联进调用方常量池)。
代码实战 (ClassInitTimingDemo.java):
java
public class ClassInitTimingDemo {
static class Parent {
static { System.out.println(" [Parent 静态代码块执行]"); }
static int value = 100;
static final int CONST = 200; // 编译期常量, 会被调用方内联
}
static class Child extends Parent {
static { System.out.println(" [Child 静态代码块执行]"); }
}
public static void main(String[] args) {
System.out.println("1) 子类访问父类的静态字段 (被动引用, 只初始化 Parent):");
System.out.println(" Child.value = " + Child.value); // 只初始化 Parent
System.out.println("2) new Child() (主动引用, 初始化 Child):");
new Child(); // 才初始化 Child
System.out.println("3) 通过子类访问编译期常量 (常量已内联, 不触发任何初始化):");
System.out.println(" Child.CONST = " + Child.CONST);
System.out.println("4) 创建子类数组 (被动引用, 不触发初始化):");
Child[] arr = new Child[3];
System.out.println(" arr.length = " + arr.length);
}
}
实测输出:
text
1) 子类访问父类的静态字段 (被动引用, 只初始化 Parent):
[Parent 静态代码块执行]
Child.value = 100
2) new Child() (主动引用, 初始化 Child):
[Child 静态代码块执行]
3) 通过子类访问编译期常量 (常量已内联, 不触发任何初始化):
Child.CONST = 200
4) 创建子类数组 (被动引用, 不触发初始化):
arr.length = 3
2.5 代码实战:自定义 ClassLoader
要点:
- 只重写
findClass,不要重写loadClass------loadClass里封装了双亲委派逻辑,重写它等于亲手打破模型; - 用
defineClass(name, bytes, 0, len)把字节数组定义为类; - 同一个全限定名被不同加载器加载,是不同的类 (
==为 false,互相强转抛ClassCastException)。
java
import java.nio.file.Path;
public class MyClassLoaderDemo {
/** 只重写 findClass, 保留 loadClass 的双亲委派逻辑 */
static class PathClassLoader extends ClassLoader {
private final Path classDir;
PathClassLoader(String name, Path classDir, ClassLoader parent) {
super(parent);
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
try {
byte[] bytes = java.nio.file.Files.readAllBytes(
classDir.resolve(className.replace('.', '/') + ".class"));
return defineClass(className, bytes, 0, bytes.length);
} catch (java.io.IOException e) {
throw new ClassNotFoundException(className, e);
}
}
}
public static void main(String[] args) throws Exception {
// 1) 三大加载器一览: Bootstrap 由 C++ 实现, Java 侧看不到实例 (打印为 null)
ClassLoader app = ClassLoader.getSystemClassLoader();
System.out.println("App(应用)加载器 : " + app);
System.out.println("Platform(平台)加载器 : " + app.getParent());
System.out.println("Bootstrap(启动)加载器: " + String.class.getClassLoader() + " (null 即由 C++ 实现)");
// 2) 双亲委派: 自定义加载器加载 java.* 仍向上委派, 最终由 Bootstrap 完成
PathClassLoader l0 = new PathClassLoader("L0", Path.of("custom"), app);
System.out.println("自定义加载器加载 java.lang.String, 实际完成者: "
+ l0.loadClass("java.lang.String").getClassLoader());
// 3) 自定义目录里的 Hello: 父加载器找不到 -> 走子加载器的 findClass
PathClassLoader l1 = new PathClassLoader("L1", Path.of("custom"), app);
PathClassLoader l2 = new PathClassLoader("L2", Path.of("custom"), app);
Class<?> c1 = l1.loadClass("Hello");
Class<?> c2 = l2.loadClass("Hello");
System.out.println("L1 加载的 Hello 来自: " + c1.getClassLoader());
System.out.println("L2 加载的 Hello 来自: " + c2.getClassLoader());
System.out.println("c1 == c2 ? " + (c1 == c2) + " <- 同一全限定名, 不同加载器 => 不同的类");
// 4) Runnable 由 Bootstrap 加载, 是全人类共同的父类型 -> 转型成功
Runnable r = (Runnable) c1.getDeclaredConstructor().newInstance();
r.run();
}
}
实测输出:
text
App(应用)加载器 : jdk.internal.loader.ClassLoaders$AppClassLoader@6bc7c054
Platform(平台)加载器 : jdk.internal.loader.ClassLoaders$PlatformClassLoader@1be6f5c3
Bootstrap(启动)加载器: null (null 即由 C++ 实现)
自定义加载器加载 java.lang.String, 实际完成者: null
L1 加载的 Hello 来自: MyClassLoaderDemo$PathClassLoader@3e3abc88
L2 加载的 Hello 来自: MyClassLoaderDemo$PathClassLoader@6ce253f1
c1 == c2 ? false <- 同一全限定名, 不同加载器 => 不同的类
[Hello 静态块] 由 MyClassLoaderDemo$PathClassLoader@3e3abc88 加载并初始化
[Hello.run] 我的类加载器: MyClassLoaderDemo$PathClassLoader@3e3abc88
⚠️ 实战陷阱 :若
Hello同时在类路径上,l1.loadClass("Hello")会被 AppClassLoader 直接命中,自定义加载器的findClass根本不会被调用------这就是为什么本 Demo 把Hello.class单独放在custom/目录。⚠️ 实战陷阱 :
(Hello) obj强转的前提是两者的加载器相同或有父子关系。跨加载器强转同一个类名会抛ClassCastException,这是 OSGi/Tomcat 类隔离问题的经典症状。
3. 运行时数据区
3.1 五大区总览
#mermaid-svg-enNDMEIzqOWb4xKo{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-enNDMEIzqOWb4xKo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-enNDMEIzqOWb4xKo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-enNDMEIzqOWb4xKo .error-icon{fill:#552222;}#mermaid-svg-enNDMEIzqOWb4xKo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-enNDMEIzqOWb4xKo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-enNDMEIzqOWb4xKo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-enNDMEIzqOWb4xKo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-enNDMEIzqOWb4xKo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-enNDMEIzqOWb4xKo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-enNDMEIzqOWb4xKo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-enNDMEIzqOWb4xKo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-enNDMEIzqOWb4xKo .marker.cross{stroke:#333333;}#mermaid-svg-enNDMEIzqOWb4xKo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-enNDMEIzqOWb4xKo p{margin:0;}#mermaid-svg-enNDMEIzqOWb4xKo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-enNDMEIzqOWb4xKo .cluster-label text{fill:#333;}#mermaid-svg-enNDMEIzqOWb4xKo .cluster-label span{color:#333;}#mermaid-svg-enNDMEIzqOWb4xKo .cluster-label span p{background-color:transparent;}#mermaid-svg-enNDMEIzqOWb4xKo .label text,#mermaid-svg-enNDMEIzqOWb4xKo span{fill:#333;color:#333;}#mermaid-svg-enNDMEIzqOWb4xKo .node rect,#mermaid-svg-enNDMEIzqOWb4xKo .node circle,#mermaid-svg-enNDMEIzqOWb4xKo .node ellipse,#mermaid-svg-enNDMEIzqOWb4xKo .node polygon,#mermaid-svg-enNDMEIzqOWb4xKo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-enNDMEIzqOWb4xKo .rough-node .label text,#mermaid-svg-enNDMEIzqOWb4xKo .node .label text,#mermaid-svg-enNDMEIzqOWb4xKo .image-shape .label,#mermaid-svg-enNDMEIzqOWb4xKo .icon-shape .label{text-anchor:middle;}#mermaid-svg-enNDMEIzqOWb4xKo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-enNDMEIzqOWb4xKo .rough-node .label,#mermaid-svg-enNDMEIzqOWb4xKo .node .label,#mermaid-svg-enNDMEIzqOWb4xKo .image-shape .label,#mermaid-svg-enNDMEIzqOWb4xKo .icon-shape .label{text-align:center;}#mermaid-svg-enNDMEIzqOWb4xKo .node.clickable{cursor:pointer;}#mermaid-svg-enNDMEIzqOWb4xKo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-enNDMEIzqOWb4xKo .arrowheadPath{fill:#333333;}#mermaid-svg-enNDMEIzqOWb4xKo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-enNDMEIzqOWb4xKo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-enNDMEIzqOWb4xKo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-enNDMEIzqOWb4xKo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-enNDMEIzqOWb4xKo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-enNDMEIzqOWb4xKo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-enNDMEIzqOWb4xKo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-enNDMEIzqOWb4xKo .cluster text{fill:#333;}#mermaid-svg-enNDMEIzqOWb4xKo .cluster span{color:#333;}#mermaid-svg-enNDMEIzqOWb4xKo 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-enNDMEIzqOWb4xKo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-enNDMEIzqOWb4xKo rect.text{fill:none;stroke-width:0;}#mermaid-svg-enNDMEIzqOWb4xKo .icon-shape,#mermaid-svg-enNDMEIzqOWb4xKo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-enNDMEIzqOWb4xKo .icon-shape p,#mermaid-svg-enNDMEIzqOWb4xKo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-enNDMEIzqOWb4xKo .icon-shape .label rect,#mermaid-svg-enNDMEIzqOWb4xKo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-enNDMEIzqOWb4xKo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-enNDMEIzqOWb4xKo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-enNDMEIzqOWb4xKo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 全局共享
堆 Heap
方法区 Method Area
(JDK 8+ 实现为元空间)
线程私有(随线程创建/销毁)
PC 寄存器
虚拟机栈
本地方法栈
| 区域 | 共享性 | 存放内容 | 溢出表现 |
|---|---|---|---|
| PC 寄存器 | 线程私有 | 当前线程执行的字节码行号 | 唯一不会 OOM 的区域 |
| 虚拟机栈 | 线程私有 | 栈帧(局部变量表、操作数栈、返回地址、动态链接) | StackOverflowError |
| 本地方法栈 | 线程私有 | 本地(native)方法执行上下文 | StackOverflowError |
| 堆 | 全局共享 | 几乎所有的对象实例、数组 | OutOfMemoryError: Java heap space |
| 方法区 | 全局共享 | 类元数据、运行时常量池、静态变量(JDK 8+ 在堆内 Class 镜像中) | OutOfMemoryError: Metaspace |
3.2 程序计数器(PC Register)
- 每个线程一份,记录当前线程正在执行的字节码指令地址(行号)。
- 执行 native 方法时值为空(undefined)。
- 它是唯一在 JVM 规范中没有规定 OOM 情况的区域------因为只是记个行号,没有存储压力。
3.3 虚拟机栈与栈帧
**栈帧(Stack Frame)**四要素:
- 局部变量表 :方法参数、局部变量,以槽(slot)计,
long/double占 2 槽; - 操作数栈 :字节码指令的工作区(如
iadd从栈顶取两个数相加); - 返回地址:方法正常/异常返回后回到调用处;
- 动态链接:指向运行时常量池中该方法的引用,支撑多态(运行期才知道调用哪个实现)。
代码实战:栈深度与 StackOverflowError (StackDepthDemo.java):
java
public class StackDepthDemo {
static int depth = 0;
static void recurse() { depth++; recurse(); }
public static void main(String[] args) {
try {
recurse();
} catch (StackOverflowError e) {
System.out.println("递归到第 " + depth + " 层时抛出 " + e.getClass().getName());
System.out.println("注意: 它是 Error, 不是 Exception, 通常不应捕获后继续运行");
}
System.out.println("捕获后程序仍可继续执行到这里");
}
}
实测输出(默认栈 vs -Xss256k):
text
# java StackDepthDemo
递归到第 22330 层时抛出 java.lang.StackOverflowError
# java -Xss256k StackDepthDemo
递归到第 2399 层时抛出 java.lang.StackOverflowError
栈容量直接决定递归深度:默认约 1MB 栈能撑 2.2 万层,-Xss256k 只剩 2400 层(约 1/9)。
⚠️ 实战陷阱 :
StackOverflowError是Error不是Exception。深递归场景(JSON 深度解析、树遍历)应改为显式栈或限制深度,而不是无脑调大-Xss------栈容量翻倍只是把崩溃点往后推,OOM 依旧。⚠️ 实战陷阱 :线程数过多时报的是
java.lang.OutOfMemoryError: unable to create native thread(操作系统线程数/内存上限),不是堆 OOM,调大-Xmx无效,要控制线程总数或调ulimit -u。
3.4 本地方法栈
- 为 JNI(Java Native Interface)调用本地方法服务。
- HotSpot 中本地方法栈与虚拟机栈合二为一,
-Xss同时约束两者。 - 本地方法里再回调 Java(如 JNI 创建线程),其 Java 栈同样受
-Xss限制。
3.5 堆(Heap):分代与对象分配流程
#mermaid-svg-ZkxF8SQr0y4PEjMk{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-ZkxF8SQr0y4PEjMk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZkxF8SQr0y4PEjMk .error-icon{fill:#552222;}#mermaid-svg-ZkxF8SQr0y4PEjMk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZkxF8SQr0y4PEjMk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZkxF8SQr0y4PEjMk .marker.cross{stroke:#333333;}#mermaid-svg-ZkxF8SQr0y4PEjMk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZkxF8SQr0y4PEjMk p{margin:0;}#mermaid-svg-ZkxF8SQr0y4PEjMk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZkxF8SQr0y4PEjMk .cluster-label text{fill:#333;}#mermaid-svg-ZkxF8SQr0y4PEjMk .cluster-label span{color:#333;}#mermaid-svg-ZkxF8SQr0y4PEjMk .cluster-label span p{background-color:transparent;}#mermaid-svg-ZkxF8SQr0y4PEjMk .label text,#mermaid-svg-ZkxF8SQr0y4PEjMk span{fill:#333;color:#333;}#mermaid-svg-ZkxF8SQr0y4PEjMk .node rect,#mermaid-svg-ZkxF8SQr0y4PEjMk .node circle,#mermaid-svg-ZkxF8SQr0y4PEjMk .node ellipse,#mermaid-svg-ZkxF8SQr0y4PEjMk .node polygon,#mermaid-svg-ZkxF8SQr0y4PEjMk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZkxF8SQr0y4PEjMk .rough-node .label text,#mermaid-svg-ZkxF8SQr0y4PEjMk .node .label text,#mermaid-svg-ZkxF8SQr0y4PEjMk .image-shape .label,#mermaid-svg-ZkxF8SQr0y4PEjMk .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZkxF8SQr0y4PEjMk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZkxF8SQr0y4PEjMk .rough-node .label,#mermaid-svg-ZkxF8SQr0y4PEjMk .node .label,#mermaid-svg-ZkxF8SQr0y4PEjMk .image-shape .label,#mermaid-svg-ZkxF8SQr0y4PEjMk .icon-shape .label{text-align:center;}#mermaid-svg-ZkxF8SQr0y4PEjMk .node.clickable{cursor:pointer;}#mermaid-svg-ZkxF8SQr0y4PEjMk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZkxF8SQr0y4PEjMk .arrowheadPath{fill:#333333;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZkxF8SQr0y4PEjMk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZkxF8SQr0y4PEjMk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZkxF8SQr0y4PEjMk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZkxF8SQr0y4PEjMk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZkxF8SQr0y4PEjMk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZkxF8SQr0y4PEjMk .cluster text{fill:#333;}#mermaid-svg-ZkxF8SQr0y4PEjMk .cluster span{color:#333;}#mermaid-svg-ZkxF8SQr0y4PEjMk 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-ZkxF8SQr0y4PEjMk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZkxF8SQr0y4PEjMk rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZkxF8SQr0y4PEjMk .icon-shape,#mermaid-svg-ZkxF8SQr0y4PEjMk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZkxF8SQr0y4PEjMk .icon-shape p,#mermaid-svg-ZkxF8SQr0y4PEjMk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZkxF8SQr0y4PEjMk .icon-shape .label rect,#mermaid-svg-ZkxF8SQr0y4PEjMk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZkxF8SQr0y4PEjMk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZkxF8SQr0y4PEjMk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZkxF8SQr0y4PEjMk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
Young GC 后仍存活
年龄达到 MaxTenuringThreshold
(默认 15)
Survivor 中同年龄对象
占 Survivor 一半以上
(动态年龄判定)
老年代也放不下
new 对象
超大对象?
(超过 Pretenuring 阈值)
直接进入老年代
TLAB
线程本地分配缓冲区
Eden 区
Survivor 0/1
(年龄 +1,两区轮换)
老年代
Full GC
- TLAB :每个线程在 Eden 中预分配一小块私有缓冲区,
new时指针碰撞分配,无线程竞争(-XX:+UseTLAB,默认开启)。 - 新生代 :Eden : Survivor 默认
8 : 1 : 1(-XX:SurvivorRatio=8),每次 Young GC 存活对象在 S0/S1 之间拷贝轮换。 - 晋升老年代 两条路径:年龄熬到 15;或 Survivor 中同年龄对象总和超过 Survivor 一半,这些对象提前晋升。
- 动态分代只是概念:G1/ZGC 等收集器不按物理连续区域分代,G1 把堆划成等大 Region 按逻辑分代,ZGC 传统上是单代(JDK 21 起分代 ZGC 恢复了逻辑分代)。
⚠️ 实战陷阱:「对象优先在 Eden 分配」是 Serial/ParNew/Parallel/CMS/G1 的口径。看到面试题「ZGC 有没有新生代老年代」------JDK 21 之前没有,JDK 21 起分代 ZGC 有了逻辑分代,但都不是物理连续的两块内存。
3.6 方法区与元空间
| 版本 | 方法区实现 | 位置 | 备注 |
|---|---|---|---|
| JDK 7 及以前 | 永久代(Permanent Generation) | 堆内 | 容易 OOM:PermGen space |
| JDK 7 | 过渡 | --- | 字符串常量池移入堆 |
| JDK 8+ | 元空间(Metaspace) | 本地内存(堆外) | -XX:MaxMetaspaceSize 默认无上限,受本机内存约束 |
- 方法区存:类元数据(类型信息、字段/方法描述、字节码)、运行时常量池、JIT 编译产物缓存等。
- JDK 8 起压缩类指针(
UseCompressedClassPointers)开启时,类数据还有一块独立的 Compressed Class Space ,默认上限 1GB(-XX:CompressedClassSpaceSize)。它和MaxMetaspaceSize是两道独立的闸门,任何一道触顶都 OOM。 - 字符串常量池(
String.intern)在 JDK 7 之后位于堆中。
代码实战:元空间 OOM (MetaspaceOomDemo.java,循环 defineClass 制造新类):
java
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
public class MetaspaceOomDemo {
/** 手写一个最小合法 class 文件 (无字段无方法), 用于快速产生新的类元数据 */
static byte[] minimalClass(String className) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos);
out.writeInt(0xCAFEBABE); // magic
out.writeShort(0); // minor version
out.writeShort(52); // major version (52 = Java 8)
out.writeShort(8); // constant_pool_count (1~7 共 7 项)
out.writeByte(1); out.writeUTF(className); // #1 Utf8 类名
out.writeByte(7); out.writeShort(1); // #2 Class -> #1
out.writeByte(1); out.writeUTF("java/lang/Object");// #3 Utf8
out.writeByte(7); out.writeShort(3); // #4 Class -> #3
out.writeByte(1); out.writeUTF("()V"); // #5 Utf8 (占位)
out.writeByte(1); out.writeUTF("SourceFile"); // #6 Utf8 (占位)
out.writeByte(1); out.writeUTF("Demo.java"); // #7 Utf8 (占位)
out.writeShort(0x0021); // access_flags: public + super
out.writeShort(2); // this_class -> #2
out.writeShort(4); // super_class -> #4
out.writeShort(0); // interfaces_count
out.writeShort(0); // fields_count
out.writeShort(0); // methods_count
out.writeShort(0); // attributes_count
out.flush();
return bos.toByteArray();
}
static class ByteClassLoader extends ClassLoader {
ByteClassLoader(ClassLoader parent) { super(parent); }
Class<?> define(String name, byte[] data) {
return defineClass(name, data, 0, data.length);
}
}
public static void main(String[] args) throws Exception {
ByteClassLoader loader = new ByteClassLoader(MetaspaceOomDemo.class.getClassLoader());
long start = System.currentTimeMillis();
long count = 0;
try {
while (System.currentTimeMillis() - start < 60_000) {
loader.define("Gen" + count, minimalClass("Gen" + count));
count++;
if (count % 5000 == 0) System.out.println("已加载 " + count + " 个类...");
}
} catch (OutOfMemoryError e) {
System.out.println("加载 " + count + " 个类后 OOM: " + e);
System.out.println("类元数据在元空间(不在堆里), 调大 -Xmx 救不了它");
System.out.println("典型诱因: CGLIB/ByteBuddy 动态代理、Groovy 脚本、热部署");
}
}
}
运行:java -XX:MaxMetaspaceSize=16m -XX:CompressedClassSpaceSize=16m MetaspaceOomDemo
实测输出:
text
已加载 5000 个类...
已加载 10000 个类...
已加载 15000 个类...
已加载 20000 个类...
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
⚠️ 实战陷阱 (本 Demo 实测踩坑):最初想用
Proxy.newProxyInstance循环生成类,结果跑了 60 秒不 OOM------Proxy 按(类加载器, 接口数组)缓存代理类 ,同一个接口永远返回同一个类,元空间根本不增长。要真正产生新类元数据,必须每次defineClass不同类名。
3.7 对象的内存布局
一个普通对象在堆中 = 对象头 + 实例数据 + 对齐填充:
| 部分 | 内容 | 大小(64 位 JVM) |
|---|---|---|
| 对象头 Mark Word | hashcode、GC 年龄、锁状态、偏向锁线程 ID | 8 字节 |
| 对象头 类型指针 | 指向类元数据 | 4 字节(开启压缩指针)/ 8 字节(关闭) |
| 实例数据 | 各字段,父类在前、子类在后,同宽字段聚在一起 | 按字段 |
| 对齐填充 | 保证 8 字节对齐 | 0~7 字节 |
- 压缩指针(
-XX:+UseCompressedOops)默认开启,此时类型指针 4 字节,堆 ≤ 32GB 均可受益;超过 32GB 自动关闭。 - 数组对象头多一个 4 字节的
length字段。 - Mark Word 是复用的:无锁态存 hashcode,加锁后依次升级为轻量级锁/重量级锁标志,GC 时存年龄与标记位。
⚠️ 实战陷阱 :
-XX:+PrintFieldLayout需要 JOL 依赖(org.openjdk.jol:jol-core)才能看每字段偏移;生产排查对象布局一律用 JOL,不要凭"int 4 字节 long 8 字节"硬算------字段重排后真实布局会出乎意料(如boolean+int组合的填充)。
3.8 逃逸分析
逃逸分两种:
- 方法逃逸:对象作为返回值/参数传给外部方法;
- 线程逃逸:对象被其他线程访问(如赋值给静态字段、放进共享集合)。
JIT 对不逃逸对象做的优化:标量替换 (把对象拆成字段直接用)、栈上分配 (HotSpot 实际只做标量替换)、锁消除 、同步省略。
代码实战:逃逸 vs 不逃逸 (EscapeDemo.java):
java
public class EscapeDemo {
/** 逃逸出口: 对象被赋值给静态字段后, 方法外部仍可访问 */
static byte[] sink;
/** 不逃逸: 数组只在本方法内使用 */
static void allocNotEscape() {
byte[] data = new byte[64];
data[0] = 1;
data[data.length - 1] = 2;
}
/** 逃逸: 数组被赋值给静态字段, 调用方之外仍可达 */
static void allocEscape() {
sink = new byte[64];
sink[0] = 1;
sink[sink.length - 1] = 2;
}
public static void main(String[] args) {
int n = 100_000_000;
long start = System.currentTimeMillis();
for (int i = 0; i < n; i++) allocNotEscape();
long t1 = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
for (int i = 0; i < n; i++) allocEscape();
long t2 = System.currentTimeMillis() - start;
System.out.printf("不逃逸分配 %,d 次: %d ms (逃逸分析生效, 对象被标量替换, 不落堆)%n", n, t1);
System.out.printf("逃逸分配 %,d 次: %d ms (每次都在堆上分配, 触发大量 Young GC)%n", n, t2);
}
}
运行:java -Xmx64m -Xlog:gc* EscapeDemo,实测输出:
text
不逃逸分配 100,000,000 次: 3 ms (逃逸分析生效, 对象被标量替换, 不落堆)
逃逸分配 100,000,000 次: 1050 ms (每次都在堆上分配, 触发大量 Young GC)
Young GC 次数: 414
1 亿次分配:不逃逸 3ms / 0 次 GC (对象被拆解,从未落堆),逃逸 1050ms / 414 次 Young GC。差距 350 倍,这就是"能不出堆就不出堆"的量化证据。
⚠️ 实战陷阱 :逃逸分析只在方法足够热(被 C1/C2 编译)后才生效,且默认不分析超过
-XX:EliminateAllocationArraySizeLimit(默认 4096 字节)的数组------想靠大数组做栈上分配是行不通的。⚠️ 实战陷阱 :任何"手写 for 循环计时"的基准测试都不可信(会被死代码消除、常量折叠、预热不足干扰)。性能对比必须用 JMH(见 5.5 节)。
3.9 OOM 类型速查表
| 报错 | 出事区域 | 常见原因 | 排查姿势 |
|---|---|---|---|
Java heap space |
堆 | 内存泄漏、堆分配过小 | dump + MAT,看 GC Roots 引用链 |
GC overhead limit exceeded |
堆 | 98%+ 时间在做 GC 却收效甚微 | 同上;先看是不是代码持有不放 |
Metaspace / Compressed class space |
元空间 | 动态代理/脚本语言大量产类 | -XX:MaxMetaspaceSize;找 CGLIB/Groovy 源头 |
Requested array size exceeds VM limit |
堆 | 数组长度接近 Integer.MAX_VALUE |
检查分配逻辑,改分页/流式 |
unable to create native thread |
OS/栈 | 线程总数超限 | 降并发线程数;调 ulimit -u |
StackOverflowError |
虚拟机栈 | 无限/过深递归 | jstack 看重复栈帧 |
4. 垃圾回收
4.1 对象存活判定:可达性分析
#mermaid-svg-B3tDg6fu9mfaAQpa{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-B3tDg6fu9mfaAQpa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-B3tDg6fu9mfaAQpa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-B3tDg6fu9mfaAQpa .error-icon{fill:#552222;}#mermaid-svg-B3tDg6fu9mfaAQpa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B3tDg6fu9mfaAQpa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-B3tDg6fu9mfaAQpa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B3tDg6fu9mfaAQpa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B3tDg6fu9mfaAQpa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-B3tDg6fu9mfaAQpa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B3tDg6fu9mfaAQpa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B3tDg6fu9mfaAQpa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B3tDg6fu9mfaAQpa .marker.cross{stroke:#333333;}#mermaid-svg-B3tDg6fu9mfaAQpa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B3tDg6fu9mfaAQpa p{margin:0;}#mermaid-svg-B3tDg6fu9mfaAQpa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B3tDg6fu9mfaAQpa .cluster-label text{fill:#333;}#mermaid-svg-B3tDg6fu9mfaAQpa .cluster-label span{color:#333;}#mermaid-svg-B3tDg6fu9mfaAQpa .cluster-label span p{background-color:transparent;}#mermaid-svg-B3tDg6fu9mfaAQpa .label text,#mermaid-svg-B3tDg6fu9mfaAQpa span{fill:#333;color:#333;}#mermaid-svg-B3tDg6fu9mfaAQpa .node rect,#mermaid-svg-B3tDg6fu9mfaAQpa .node circle,#mermaid-svg-B3tDg6fu9mfaAQpa .node ellipse,#mermaid-svg-B3tDg6fu9mfaAQpa .node polygon,#mermaid-svg-B3tDg6fu9mfaAQpa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B3tDg6fu9mfaAQpa .rough-node .label text,#mermaid-svg-B3tDg6fu9mfaAQpa .node .label text,#mermaid-svg-B3tDg6fu9mfaAQpa .image-shape .label,#mermaid-svg-B3tDg6fu9mfaAQpa .icon-shape .label{text-anchor:middle;}#mermaid-svg-B3tDg6fu9mfaAQpa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-B3tDg6fu9mfaAQpa .rough-node .label,#mermaid-svg-B3tDg6fu9mfaAQpa .node .label,#mermaid-svg-B3tDg6fu9mfaAQpa .image-shape .label,#mermaid-svg-B3tDg6fu9mfaAQpa .icon-shape .label{text-align:center;}#mermaid-svg-B3tDg6fu9mfaAQpa .node.clickable{cursor:pointer;}#mermaid-svg-B3tDg6fu9mfaAQpa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-B3tDg6fu9mfaAQpa .arrowheadPath{fill:#333333;}#mermaid-svg-B3tDg6fu9mfaAQpa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B3tDg6fu9mfaAQpa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B3tDg6fu9mfaAQpa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B3tDg6fu9mfaAQpa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-B3tDg6fu9mfaAQpa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B3tDg6fu9mfaAQpa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-B3tDg6fu9mfaAQpa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B3tDg6fu9mfaAQpa .cluster text{fill:#333;}#mermaid-svg-B3tDg6fu9mfaAQpa .cluster span{color:#333;}#mermaid-svg-B3tDg6fu9mfaAQpa 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-B3tDg6fu9mfaAQpa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-B3tDg6fu9mfaAQpa rect.text{fill:none;stroke-width:0;}#mermaid-svg-B3tDg6fu9mfaAQpa .icon-shape,#mermaid-svg-B3tDg6fu9mfaAQpa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B3tDg6fu9mfaAQpa .icon-shape p,#mermaid-svg-B3tDg6fu9mfaAQpa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-B3tDg6fu9mfaAQpa .icon-shape .label rect,#mermaid-svg-B3tDg6fu9mfaAQpa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B3tDg6fu9mfaAQpa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-B3tDg6fu9mfaAQpa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-B3tDg6fu9mfaAQpa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不可达
对象C
对象D
可达
GC Roots
对象A
对象B
主流 JVM 用可达性分析(不用引用计数法,因为它解决不了循环引用):从 GC Roots 出发沿引用链遍历,遍历不到的就是垃圾------哪怕两个对象互相抱着(图中的 C/D)也照回收。
GC Roots 包括:
- 虚拟机栈中局部变量表引用的对象;
- 方法区中静态属性、常量引用的对象;
- 本地方法栈中 JNI 引用的对象;
- 虚拟机内部引用(常驻异常、系统类加载器等);
- 被同步锁(synchronized)持有的对象;
- JMX 注册表、JVMTI 等注册对象。
4.2 四种引用类型
| 引用 | 声明 | 回收时机 | 典型用途 |
|---|---|---|---|
| 强引用 | Object o = new Object() |
永不回收(只要可达) | 普通对象 |
| 软引用 | SoftReference |
内存不足时才回收 | 缓存(图片/网页缓存) |
| 弱引用 | WeakReference |
下一次 GC 必然回收 | WeakHashMap、ThreadLocal 底层 |
| 虚引用 | PhantomReference |
无法获取对象,仅感知回收 | 堆外内存回收监控 |
代码实战:四种引用 + ReferenceQueue (ReferenceTypeDemo.java):
java
import java.lang.ref.*;
public class ReferenceTypeDemo {
public static void main(String[] args) throws Exception {
Object strong = new Object(); // 强引用
SoftReference<byte[]> soft = new SoftReference<>(new byte[1024 * 1024]); // 软引用
WeakReference<Object> weak = new WeakReference<>(new Object()); // 弱引用
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), queue); // 虚引用
System.out.println("GC 前: strong 存在 = " + (strong != null)
+ ", soft.get() 非空 = " + (soft.get() != null)
+ ", weak.get() 非空 = " + (weak.get() != null)
+ ", phantom.get() = " + phantom.get());
System.gc(); // 仅为演示主动触发, 生产环境慎用
Thread.sleep(100);
System.out.println("GC 后: soft.get() 非空 = " + (soft.get() != null)
+ " (内存充足, 软引用存活)");
System.out.println("GC 后: weak.get() = " + weak.get() + " (弱引用已被清除)");
System.out.println("GC 后: queue.poll() = " + queue.poll()
+ " (虚引用已入队, 说明对象已被回收)");
}
}
实测输出:
text
GC 前: strong 存在 = true, soft.get() 非空 = true, weak.get() 非空 = true, phantom.get() = null
GC 后: soft.get() 非空 = true (内存充足, 软引用存活)
GC 后: weak.get() = null (弱引用已被清除)
GC 后: queue.poll() = java.lang.ref.PhantomReference@2d98a335 (虚引用已入队, 说明对象已被回收)
代码实战:WeakHashMap 的静默消失陷阱 (WeakHashMapDemo.java):key 是弱引用,key 对象失去外部强引用后,整个条目会被 GC 悄悄删掉。
java
Map<Key, String> map = new WeakHashMap<>();
Key k1 = new Key("k1");
map.put(k1, "v1");
map.put(new Key("k2"), "v2"); // 陷阱: 临时 key 没有任何强引用
System.out.println("GC 前 size = " + map.size());
k1 = null;
System.gc();
Thread.sleep(100);
System.out.println("GC 后 size = " + map.size() + " (k1 与 k2 的条目都消失了)");
实测输出:
text
GC 前 size = 2
GC 后 size = 0 (k1 与 k2 的条目都消失了)
⚠️ 实战陷阱 :
WeakHashMap做缓存时,key 必须被外部强引用持有 (如map.put(dbId, value)里的dbId要是个存活变量)。map.put(new Key("x"), v)这种写法,条目随时消失,且不报任何错------缓存击穿问题就是这么来的。
4.3 垃圾回收算法
| 算法 | 做法 | 优点 | 缺点 | 用在哪 |
|---|---|---|---|---|
| 标记-清除 | 标记存活,清除死亡 | 简单 | 内存碎片 | CMS 的并发清除阶段 |
| 标记-复制 | 两块内存,存活拷贝到另一块 | 无碎片、高效 | 浪费一半空间 | 新生代(S0/S1) |
| 标记-整理 | 标记后把存活对象挪到一端 | 无碎片 | 整理有开销 | 老年代(Parallel/Serial Old) |
分代收集理论建立在两个假说上:绝大多数对象朝生夕灭(弱分代假说);熬过越多次 GC 的对象越难死亡(强分代假说)。所以新生代用复制(死得多、拷得少),老年代用标记-清除/整理(活得多、拷不起)。
4.4 STW 与安全点
- Stop-The-World(STW) :GC 的某些阶段必须暂停所有 Java 线程,保证对象引用关系不变。GC 调优的本质就是压缩 STW 总时长与频率。
- 安全点(SafePoint):线程不是想停就停,只能停在安全点(方法调用前、循环跳转处、异常抛出等)。线程跑到安全点附近才会响应"GC 邀请"。
- 安全区(SafeRegion):一段引用关系不会变化的代码区,线程在此范围内可自由跑,要 GC 时等它出去(解决 sleep/blocked 线程到不了安全点的问题)。
⚠️ 实战陷阱 :
Thread.sleep(10000)里的线程到不了安全点,GC 会等------但这是正常设计不是 bug。真正要警惕的是长时间 sleep/阻塞的线程拖慢 GC 停顿观测,做延迟敏感的压测时要排除这类干扰。
4.5 收集器家族对比
#mermaid-svg-rnQRG5MBsZvSD540{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-rnQRG5MBsZvSD540 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rnQRG5MBsZvSD540 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rnQRG5MBsZvSD540 .error-icon{fill:#552222;}#mermaid-svg-rnQRG5MBsZvSD540 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rnQRG5MBsZvSD540 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rnQRG5MBsZvSD540 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rnQRG5MBsZvSD540 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rnQRG5MBsZvSD540 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rnQRG5MBsZvSD540 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rnQRG5MBsZvSD540 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rnQRG5MBsZvSD540 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rnQRG5MBsZvSD540 .marker.cross{stroke:#333333;}#mermaid-svg-rnQRG5MBsZvSD540 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rnQRG5MBsZvSD540 p{margin:0;}#mermaid-svg-rnQRG5MBsZvSD540 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rnQRG5MBsZvSD540 .cluster-label text{fill:#333;}#mermaid-svg-rnQRG5MBsZvSD540 .cluster-label span{color:#333;}#mermaid-svg-rnQRG5MBsZvSD540 .cluster-label span p{background-color:transparent;}#mermaid-svg-rnQRG5MBsZvSD540 .label text,#mermaid-svg-rnQRG5MBsZvSD540 span{fill:#333;color:#333;}#mermaid-svg-rnQRG5MBsZvSD540 .node rect,#mermaid-svg-rnQRG5MBsZvSD540 .node circle,#mermaid-svg-rnQRG5MBsZvSD540 .node ellipse,#mermaid-svg-rnQRG5MBsZvSD540 .node polygon,#mermaid-svg-rnQRG5MBsZvSD540 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rnQRG5MBsZvSD540 .rough-node .label text,#mermaid-svg-rnQRG5MBsZvSD540 .node .label text,#mermaid-svg-rnQRG5MBsZvSD540 .image-shape .label,#mermaid-svg-rnQRG5MBsZvSD540 .icon-shape .label{text-anchor:middle;}#mermaid-svg-rnQRG5MBsZvSD540 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rnQRG5MBsZvSD540 .rough-node .label,#mermaid-svg-rnQRG5MBsZvSD540 .node .label,#mermaid-svg-rnQRG5MBsZvSD540 .image-shape .label,#mermaid-svg-rnQRG5MBsZvSD540 .icon-shape .label{text-align:center;}#mermaid-svg-rnQRG5MBsZvSD540 .node.clickable{cursor:pointer;}#mermaid-svg-rnQRG5MBsZvSD540 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rnQRG5MBsZvSD540 .arrowheadPath{fill:#333333;}#mermaid-svg-rnQRG5MBsZvSD540 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rnQRG5MBsZvSD540 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rnQRG5MBsZvSD540 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rnQRG5MBsZvSD540 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rnQRG5MBsZvSD540 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rnQRG5MBsZvSD540 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rnQRG5MBsZvSD540 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rnQRG5MBsZvSD540 .cluster text{fill:#333;}#mermaid-svg-rnQRG5MBsZvSD540 .cluster span{color:#333;}#mermaid-svg-rnQRG5MBsZvSD540 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-rnQRG5MBsZvSD540 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rnQRG5MBsZvSD540 rect.text{fill:none;stroke-width:0;}#mermaid-svg-rnQRG5MBsZvSD540 .icon-shape,#mermaid-svg-rnQRG5MBsZvSD540 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rnQRG5MBsZvSD540 .icon-shape p,#mermaid-svg-rnQRG5MBsZvSD540 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rnQRG5MBsZvSD540 .icon-shape .label rect,#mermaid-svg-rnQRG5MBsZvSD540 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rnQRG5MBsZvSD540 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rnQRG5MBsZvSD540 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rnQRG5MBsZvSD540 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} JDK 1.3
Serial
JDK 5
CMS 低延迟先行者
JDK 6
Parallel 吞吐量优先
JDK 9
G1 成为默认
JDK 11
ZGC 实验性
JDK 15
ZGC/Shenandoah 转正
JDK 21
分代 ZGC
| 收集器 | 算法 | 线程 | STW 特点 | 适用场景 | JDK 21 状态 |
|---|---|---|---|---|---|
| Serial | 标记-复制 + 标记-整理 | 单线程 | 停顿长,但无线程切换开销 | 客户端模式、几十 MB 小堆 | 可用 |
| Parallel | 同 Serial,多线程 | 多线程 | 停顿较长,吞吐量最高 | 后台计算、批处理,追求吞吐 | 可用 |
| CMS | 标记-清除 | 并发 | 停顿短,但有浮动垃圾、碎片 | 互联网早期低延迟场景 | 已移除(JDK 14) |
| G1 | 分 Region 标记-整理,混合回收 | 并发 | 可预测停顿(默认目标 200ms) | 大堆(4GB+)通用首选 | 默认 |
| ZGC | 染色指针 + 读屏障,并发整理 | 并发 | 亚毫秒级,与堆大小近似无关 | 超大堆低延迟 | 可用(JDK 21 支持分代) |
| Shenandoah | Brooks 转发指针 + 读屏障 | 并发 | 停顿同样与堆大小近似无关 | 超大堆低延迟 | 可用(JDK 15 转正) |
| Epsilon | 什么都不回收 | --- | 无 GC 停顿 | 短生命周期任务/性能测试基线 | 实验性 |
选择建议:
- 默认 G1 不动,90% 的应用不需要换收集器;
- 堆 ≥ 16GB 且停顿敏感 → 试 ZGC(
-XX:+UseZGC -XX:+ZGenerational,JDK 21 分代版); - 纯吞吐优先(离线报表、大数据批处理)→ Parallel;
- 调优前先有数据(GC 日志),再动参数。
4.6 GC 日志解读(JDK 9+ 统一日志)
开启方式:-Xlog:gc*:file=./gc.log:time,uptime,level,tags:filecount=5,filesize=20m
日志格式拆解(实测样本,来自 EscapeDemo 运行):
text
[0.076s][info][gc,start ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
[0.077s][info][gc ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 23M->0M(64M) 1.247ms
| 片段 | 含义 |
|---|---|
[0.076s] |
时间戳(uptime:JVM 启动后经过的时间) |
[info][gc] |
日志级别 + 标签集 |
GC(0) |
第 0 次 GC 事件 |
Pause Young (Normal) |
事件类型:新生代 GC |
(G1 Evacuation Pause) |
G1 的具体名称(并行时是 (Allocation Failure) 等) |
23M->0M(64M) |
GC 前堆占用 → GC 后堆占用(堆上限) |
1.247ms |
本次 STW 停顿时长 |
代码实战 (GcLogDemo.java,5 秒内持续分配 256KB 临时对象):实测 5 秒分配 38.4GB ,触发 674 次 Young GC ------对象创建速率是 GC 压力的根源,日志里 23M->0M 说明每次回收后 Eden 被清空,符合"朝生夕灭"。
⚠️ 实战陷阱 :日志写满磁盘是真实的生产事故。
-Xlog一定要带filecount+filesize滚动策略,否则高频 GC 的应用几天就能把日志盘写满。
4.7 GC 实战陷阱汇总
System.gc()是定时炸弹 :生产代码里调一次就是一次 Full GC。更隐蔽的是 RMI 的分布式 GC 会周期性触发System.gc()------关掉它:-XX:+DisableExplicitGC。finalize()已废弃 :JDK 9 起废弃、JDK 18 起标记"即将移除",JDK 21 仍能跑但随时可能消失。对象复活/清理逻辑请改用Cleaner或PhantomReference。- CMS 的遗产 :老系统还有
-XX:+UseConcMarkSweepGC参数,JDK 21 直接启动报错,迁移时改成 G1 或 ZGC。 - 停顿目标别拍脑袋 :
-XX:MaxGCPauseMillis=10对 64GB 堆可能逼着 G1 频繁小批回收,吞吐崩掉。停顿目标是"努力方向",不是合同。 - 伪共享(False Sharing) :多线程频繁写相邻字段会互相失效缓存行,可用
@Contended(需-XX:-RestrictContended)缓解------这是并发包的常用手段。
5. JIT 编译与执行引擎
5.1 解释器 vs JIT
| 执行方式 | 启动速度 | 峰值性能 | 说明 |
|---|---|---|---|
| 解释执行 | 快 | 慢 | 逐条翻译字节码为机器码执行 |
| C1 编译(Client) | 中 | 中 | 编译快、优化简单 |
| C2 编译(Server) | 慢 | 高 | 编译慢、深度优化 |
HotSpot 是混合模式 (java -version 输出里的 mixed mode):热点代码走 JIT,冷代码走解释。
5.2 分层编译(Tiered Compilation)
#mermaid-svg-xnCBvqy2RpFXP1V6{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-xnCBvqy2RpFXP1V6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xnCBvqy2RpFXP1V6 .error-icon{fill:#552222;}#mermaid-svg-xnCBvqy2RpFXP1V6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xnCBvqy2RpFXP1V6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xnCBvqy2RpFXP1V6 .marker.cross{stroke:#333333;}#mermaid-svg-xnCBvqy2RpFXP1V6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xnCBvqy2RpFXP1V6 p{margin:0;}#mermaid-svg-xnCBvqy2RpFXP1V6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xnCBvqy2RpFXP1V6 .cluster-label text{fill:#333;}#mermaid-svg-xnCBvqy2RpFXP1V6 .cluster-label span{color:#333;}#mermaid-svg-xnCBvqy2RpFXP1V6 .cluster-label span p{background-color:transparent;}#mermaid-svg-xnCBvqy2RpFXP1V6 .label text,#mermaid-svg-xnCBvqy2RpFXP1V6 span{fill:#333;color:#333;}#mermaid-svg-xnCBvqy2RpFXP1V6 .node rect,#mermaid-svg-xnCBvqy2RpFXP1V6 .node circle,#mermaid-svg-xnCBvqy2RpFXP1V6 .node ellipse,#mermaid-svg-xnCBvqy2RpFXP1V6 .node polygon,#mermaid-svg-xnCBvqy2RpFXP1V6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xnCBvqy2RpFXP1V6 .rough-node .label text,#mermaid-svg-xnCBvqy2RpFXP1V6 .node .label text,#mermaid-svg-xnCBvqy2RpFXP1V6 .image-shape .label,#mermaid-svg-xnCBvqy2RpFXP1V6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-xnCBvqy2RpFXP1V6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xnCBvqy2RpFXP1V6 .rough-node .label,#mermaid-svg-xnCBvqy2RpFXP1V6 .node .label,#mermaid-svg-xnCBvqy2RpFXP1V6 .image-shape .label,#mermaid-svg-xnCBvqy2RpFXP1V6 .icon-shape .label{text-align:center;}#mermaid-svg-xnCBvqy2RpFXP1V6 .node.clickable{cursor:pointer;}#mermaid-svg-xnCBvqy2RpFXP1V6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xnCBvqy2RpFXP1V6 .arrowheadPath{fill:#333333;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xnCBvqy2RpFXP1V6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xnCBvqy2RpFXP1V6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xnCBvqy2RpFXP1V6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xnCBvqy2RpFXP1V6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xnCBvqy2RpFXP1V6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xnCBvqy2RpFXP1V6 .cluster text{fill:#333;}#mermaid-svg-xnCBvqy2RpFXP1V6 .cluster span{color:#333;}#mermaid-svg-xnCBvqy2RpFXP1V6 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-xnCBvqy2RpFXP1V6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xnCBvqy2RpFXP1V6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-xnCBvqy2RpFXP1V6 .icon-shape,#mermaid-svg-xnCBvqy2RpFXP1V6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xnCBvqy2RpFXP1V6 .icon-shape p,#mermaid-svg-xnCBvqy2RpFXP1V6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xnCBvqy2RpFXP1V6 .icon-shape .label rect,#mermaid-svg-xnCBvqy2RpFXP1V6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xnCBvqy2RpFXP1V6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xnCBvqy2RpFXP1V6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xnCBvqy2RpFXP1V6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 方法变冷 / 去优化
0 层: 解释执行
1 层: C1 简单编译
(无性能统计)
2 层: C1 有限统计
3 层: C1 完整统计
4 层: C2 深度优化
默认开启(-XX:+TieredCompilation)。方法从解释开始,随调用次数增加被 C1 逐级编译,最终热点方法进入 C2 拿到最优机器码;若假设失效(如多态中从未出现的类型突然出现),发生去优化(deoptimization),回退到低层甚至解释执行。
代码实战:观察 JIT 编译 (JitWarmupDemo.java):
java
public class JitWarmupDemo {
static long sink;
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 200_000; i++) sink += compute(i);
System.out.printf("sink = %d, 总耗时 %d ms%n", sink, System.currentTimeMillis() - start);
}
/** 被反复调用的热点方法, 会被 C1/C2 逐级编译 */
static long compute(int x) {
long s = 0;
for (int i = 0; i < 100; i++) s += (long) i * x;
return s;
}
}
运行 java -XX:+PrintCompilation JitWarmupDemo,实测输出(节选):
text
110 56 2 JitWarmupDemo::compute (26 bytes)
114 57 % 4 JitWarmupDemo::compute @ 4 (26 bytes)
116 58 4 JitWarmupDemo::compute (26 bytes)
117 56 2 JitWarmupDemo::compute (26 bytes) made not entrant
解读:
110 56 2 ... compute:第 2 层(C1)编译了compute;116 58 4 ... compute:升到第 4 层(C2)重新编译;117 56 2 ... made not entrant:旧的 C1 版本被标记为不再进入(被 C2 版取代)。
5.3 热点探测
- 方法调用计数器 :方法被调用的次数,经典阈值
-XX:CompileThreshold=10000(分层编译下改由各 Tier 阈值控制); - 回边计数器:循环执行次数,循环体热点也能触发编译;
- 热度会随时间衰减(半衰期机制),防止一次性的热点长期霸占编译资源。
5.4 核心优化手段
| 优化 | 效果 | 相关参数 |
|---|---|---|
| 方法内联 | 消除调用开销,为其他优化开门 | -XX:MaxInlineSize=35、-XX:FreqInlineSize=325 |
| 逃逸分析 | 标量替换/栈上分配/锁消除 | -XX:+DoEscapeAnalysis(默认开) |
| 公共子表达式消除 | 相同计算只做一次 | --- |
| 数组边界检查消除 | 热循环去掉 bounds check | --- |
| 锁消除/锁粗化 | 无竞争锁直接去掉;相邻同步块合并 | --- |
⚠️ 实战陷阱 :反射、大量多态、超大方法 都逃不过"内联失败"或优化降级。
invokevirtual只有单态接收者时才可能内联,出现第二类型就退化为虚表查找------接口回调密集的框架(策略模式)反而要做好抽象控制。
5.5 基准测试必须用 JMH
手写 for 循环计时的基准测试会被 JIT 花样戏耍:死代码消除(算完没人用,整个循环被删)、常量折叠(输入固定,结果编译期算好)、预热不足(测的是解释执行)。
JMH 骨架 (需引入依赖 org.openjdk.jmh:jmh-core,通过 maven-shade-plugin 打包运行):
java
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class MyBenchmark {
@Benchmark
public void testStringConcat(Blackhole bh) {
String s = "a" + "b" + System.nanoTime(); // 结果喂给 Blackhole, 防止被 DCE
bh.consume(s);
}
@Benchmark
public void testStringBuilder(Blackhole bh) {
StringBuilder sb = new StringBuilder().append("a").append("b").append(System.nanoTime());
bh.consume(sb.toString());
}
}
6. 线程模型与虚拟线程(JDK 21)
6.1 平台线程:1:1 映射
每个 java.lang.Thread 对应一个操作系统内核线程。创建/切换成本高(栈默认约 1MB,上下文切换需要陷入内核),因此"线程池 + 有界队列"成了传统 IO 并发的标准姿势。
6.2 虚拟线程:M:N 调度
JDK 21 正式特性(JEP 444):
- 虚拟线程由 JVM 调度,跑在少量**载体线程(carrier)**上,载体默认是并行度 = CPU 核数的 ForkJoinPool;
- 虚拟线程阻塞(IO、sleep)时从载体卸载,让出载体给其他虚拟线程------阻塞不再消耗线程资源;
- 每任务一线程 成为新范式:
Executors.newVirtualThreadPerTaskExecutor(),不需要池化。
代码实战:虚拟线程 vs 平台线程池 (VirtualThreadDemo.java,10 万个「阻塞 10ms」任务):
java
static final int TASK_COUNT = 100_000;
static long runVirtual() throws Exception {
long start = System.currentTimeMillis();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < TASK_COUNT; i++) executor.submit(() -> sleep(10));
} // close() 会等待全部任务完成
return System.currentTimeMillis() - start;
}
static long runPlatform() throws Exception {
long start = System.currentTimeMillis();
try (ExecutorService executor = Executors.newFixedThreadPool(200)) {
for (int i = 0; i < TASK_COUNT; i++) executor.submit(() -> sleep(10));
}
return System.currentTimeMillis() - start;
}
实测输出:
text
100,000 个任务 (每个阻塞 10ms):
虚拟线程 (每任务一线程): 268 ms
平台线程 (200 线程的池): 7816 ms
268ms vs 7816ms------29 倍差距。理论上界:虚拟线程版 ≈ 单次阻塞时间(10ms)+ 调度开销;线程池版 = 任务数/池大小 × 单次阻塞(500×10ms=5s)。
6.3 实战陷阱:synchronized 与 pinning(JDK 21 特有)
JDK 21 的已知限制:虚拟线程在 synchronized 块中发生阻塞(sleep、阻塞 IO、Object.wait)时无法卸载 ,会"钉死(pin)"在载体线程上,把 M:N 打回 1:1。用 ReentrantLock(基于 LockSupport.park)则能正常卸载。
代码实战 (PinningDemo.java):64 个任务各阻塞 200ms,两版都用每任务独立的锁(无争用) ,唯一差别是 synchronized vs ReentrantLock:
java
static long runWithSynchronized() throws Exception {
long start = System.currentTimeMillis();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < TASK_COUNT; i++) {
final Object lock = new Object(); // 每个任务自己的锁: 无争用
executor.submit(() -> {
synchronized (lock) { // 但在 synchronized 中阻塞 -> pin
sleep(SLEEP_MS);
}
});
}
}
return System.currentTimeMillis() - start;
}
实测输出:
text
可用处理器数: 20 (载体线程数)
64 个任务各阻塞 200ms (每任务独立锁, 无争用):
synchronized 阻塞: 823 ms (虚拟线程被 pin 住, 20 个载体只能分批执行)
ReentrantLock 阻塞: 204 ms (阻塞时卸载, 全部并发执行)
823ms vs 204ms(约 4 倍,与 20 个载体的分批系数吻合)。用 -Djdk.tracePinnedThreads=full 还能抓到 pin 现场:
text
java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393)
PinningDemo.sleep(PinningDemo.java:63)
PinningDemo.lambda$runWithSynchronized$0(PinningDemo.java:35) <== monitors:1
<== monitors:1 明确告诉你:这个虚拟线程持有一个监视器时被阻塞,所以被 pin 了。
⚠️ 实战陷阱 :JDK 21 上虚拟线程 +
synchronized+ 阻塞操作 = 性能陷阱。三条规避路径:① 锁改用ReentrantLock;② JDK 24+ 已修复该限制(JEP 491),能升则升;③ 用-Djdk.tracePinnedThreads=full在测试环境扫一遍存量代码。
6.4 虚拟线程与 ThreadLocal(线程安全视角)
- 每个虚拟线程可以有自己的 ThreadLocal (存在线程对象里,不在栈上),100 万个虚拟线程各挂一个 ThreadLocal = 内存灾难。池化时代"借线程还线程"的清理惯性(
finally里remove())在虚拟线程时代依然要保留。 - JDK 21 预览了两个更好的替代:Scoped Values (JEP 446,不可变、按作用域继承)和 Structured Concurrency(JEP 453,任务生命周期与作用域绑定)。
- 虚拟线程不是为了提速 CPU 密集型任务------计算密集场景用平台线程 + 合理并行度即可。
- 平台线程的
synchronized/原子类/并发工具在虚拟线程上语义不变,已有的线程安全代码不会因此出错;变化的是"阻塞的代价"。
7. 性能监控与调优
7.1 命令行工具速查
| 工具 | 用途 | 示例 |
|---|---|---|
jps |
列出 Java 进程 | jps -l |
jstat |
统计 GC/类加载趋势 | jstat -gcutil <pid> 1000(每秒一次) |
jinfo |
查看/修改运行期参数 | jinfo -flag MaxHeapSize <pid> |
jmap |
堆对象统计 / dump | jmap -histo:live <pid>(会触发 Full GC) |
jstack |
线程快照(找死锁/CPU 飙高) | jstack <pid> > thread.log |
jcmd |
瑞士军刀(推荐) | jcmd <pid> Thread.print、jcmd <pid> GC.class_histogram |
jhsdb |
事后调试(attach 失败时用) | jhsdb jstack --pid <pid> |
⚠️ 实战陷阱 :
jmap -histo:live和jmap -dump都会触发 Full GC(STW),大堆上可能造成秒级停顿。生产取证优先-XX:+HeapDumpOnOutOfMemoryError自动 dump,或jcmd GC.class_histogram(较轻量)。Windows 上从 Git Bash 拿到的 PID 与$!可能对不上,jstack/jcmd报no such process时换 PowerShell 或任务管理器核对 PID。
7.2 常用参数速查
| 类别 | 参数 | 说明 |
|---|---|---|
| 堆 | -Xms2g -Xmx2g |
初始/最大堆,生产建议设相等(免扩容抖动) |
| 新生代 | -Xmn1g、-XX:SurvivorRatio=8 |
G1 用 -XX:G1NewSizePercent / G1MaxNewSizePercent |
| 栈 | -Xss1m |
线程栈大小,决定递归深度 |
| 元空间 | -XX:MaxMetaspaceSize=256m |
默认无上限,建议显式设限防吃光本机内存 |
| 收集器 | -XX:+UseG1GC / -XX:+UseZGC -XX:+ZGenerational |
JDK 21 可选:Serial/Parallel/G1/ZGC/Shenandoah |
| 停顿目标 | -XX:MaxGCPauseMillis=200 |
G1 软目标,默认 200ms |
| GC 日志 | -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=20m |
JDK 9+ 统一日志,务必加滚动 |
| OOM dump | -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump/ |
线上必备 |
| 显式 GC | -XX:+DisableExplicitGC |
屏蔽 System.gc() |
| 容器 | -XX:MaxRAMPercentage=75.0 |
容器内按比例自适应(JDK 10+ 默认开启容器支持) |
| 编译 | -XX:+TieredCompilation、-XX:+PrintCompilation |
默认开启;后者观察编译行为 |
7.3 排查实战流程
CPU 飙高:
#mermaid-svg-nvaF87G6FWtKowaB{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-nvaF87G6FWtKowaB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nvaF87G6FWtKowaB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nvaF87G6FWtKowaB .error-icon{fill:#552222;}#mermaid-svg-nvaF87G6FWtKowaB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nvaF87G6FWtKowaB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nvaF87G6FWtKowaB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nvaF87G6FWtKowaB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nvaF87G6FWtKowaB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nvaF87G6FWtKowaB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nvaF87G6FWtKowaB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nvaF87G6FWtKowaB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nvaF87G6FWtKowaB .marker.cross{stroke:#333333;}#mermaid-svg-nvaF87G6FWtKowaB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nvaF87G6FWtKowaB p{margin:0;}#mermaid-svg-nvaF87G6FWtKowaB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nvaF87G6FWtKowaB .cluster-label text{fill:#333;}#mermaid-svg-nvaF87G6FWtKowaB .cluster-label span{color:#333;}#mermaid-svg-nvaF87G6FWtKowaB .cluster-label span p{background-color:transparent;}#mermaid-svg-nvaF87G6FWtKowaB .label text,#mermaid-svg-nvaF87G6FWtKowaB span{fill:#333;color:#333;}#mermaid-svg-nvaF87G6FWtKowaB .node rect,#mermaid-svg-nvaF87G6FWtKowaB .node circle,#mermaid-svg-nvaF87G6FWtKowaB .node ellipse,#mermaid-svg-nvaF87G6FWtKowaB .node polygon,#mermaid-svg-nvaF87G6FWtKowaB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nvaF87G6FWtKowaB .rough-node .label text,#mermaid-svg-nvaF87G6FWtKowaB .node .label text,#mermaid-svg-nvaF87G6FWtKowaB .image-shape .label,#mermaid-svg-nvaF87G6FWtKowaB .icon-shape .label{text-anchor:middle;}#mermaid-svg-nvaF87G6FWtKowaB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nvaF87G6FWtKowaB .rough-node .label,#mermaid-svg-nvaF87G6FWtKowaB .node .label,#mermaid-svg-nvaF87G6FWtKowaB .image-shape .label,#mermaid-svg-nvaF87G6FWtKowaB .icon-shape .label{text-align:center;}#mermaid-svg-nvaF87G6FWtKowaB .node.clickable{cursor:pointer;}#mermaid-svg-nvaF87G6FWtKowaB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nvaF87G6FWtKowaB .arrowheadPath{fill:#333333;}#mermaid-svg-nvaF87G6FWtKowaB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nvaF87G6FWtKowaB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nvaF87G6FWtKowaB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nvaF87G6FWtKowaB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nvaF87G6FWtKowaB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nvaF87G6FWtKowaB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nvaF87G6FWtKowaB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nvaF87G6FWtKowaB .cluster text{fill:#333;}#mermaid-svg-nvaF87G6FWtKowaB .cluster span{color:#333;}#mermaid-svg-nvaF87G6FWtKowaB 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-nvaF87G6FWtKowaB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nvaF87G6FWtKowaB rect.text{fill:none;stroke-width:0;}#mermaid-svg-nvaF87G6FWtKowaB .icon-shape,#mermaid-svg-nvaF87G6FWtKowaB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nvaF87G6FWtKowaB .icon-shape p,#mermaid-svg-nvaF87G6FWtKowaB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nvaF87G6FWtKowaB .icon-shape .label rect,#mermaid-svg-nvaF87G6FWtKowaB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nvaF87G6FWtKowaB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nvaF87G6FWtKowaB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nvaF87G6FWtKowaB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CPU 飙高
top 定位进程
top -Hp 定位线程
printf '%x' 转十六进制 nid
jstack 找对应 nid 的栈
定位到业务代码热点
内存问题:
#mermaid-svg-ZgkNKLFIDXmp8v7A{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-ZgkNKLFIDXmp8v7A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZgkNKLFIDXmp8v7A .error-icon{fill:#552222;}#mermaid-svg-ZgkNKLFIDXmp8v7A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZgkNKLFIDXmp8v7A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZgkNKLFIDXmp8v7A .marker.cross{stroke:#333333;}#mermaid-svg-ZgkNKLFIDXmp8v7A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZgkNKLFIDXmp8v7A p{margin:0;}#mermaid-svg-ZgkNKLFIDXmp8v7A .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZgkNKLFIDXmp8v7A .cluster-label text{fill:#333;}#mermaid-svg-ZgkNKLFIDXmp8v7A .cluster-label span{color:#333;}#mermaid-svg-ZgkNKLFIDXmp8v7A .cluster-label span p{background-color:transparent;}#mermaid-svg-ZgkNKLFIDXmp8v7A .label text,#mermaid-svg-ZgkNKLFIDXmp8v7A span{fill:#333;color:#333;}#mermaid-svg-ZgkNKLFIDXmp8v7A .node rect,#mermaid-svg-ZgkNKLFIDXmp8v7A .node circle,#mermaid-svg-ZgkNKLFIDXmp8v7A .node ellipse,#mermaid-svg-ZgkNKLFIDXmp8v7A .node polygon,#mermaid-svg-ZgkNKLFIDXmp8v7A .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZgkNKLFIDXmp8v7A .rough-node .label text,#mermaid-svg-ZgkNKLFIDXmp8v7A .node .label text,#mermaid-svg-ZgkNKLFIDXmp8v7A .image-shape .label,#mermaid-svg-ZgkNKLFIDXmp8v7A .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZgkNKLFIDXmp8v7A .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZgkNKLFIDXmp8v7A .rough-node .label,#mermaid-svg-ZgkNKLFIDXmp8v7A .node .label,#mermaid-svg-ZgkNKLFIDXmp8v7A .image-shape .label,#mermaid-svg-ZgkNKLFIDXmp8v7A .icon-shape .label{text-align:center;}#mermaid-svg-ZgkNKLFIDXmp8v7A .node.clickable{cursor:pointer;}#mermaid-svg-ZgkNKLFIDXmp8v7A .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZgkNKLFIDXmp8v7A .arrowheadPath{fill:#333333;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZgkNKLFIDXmp8v7A .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZgkNKLFIDXmp8v7A .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZgkNKLFIDXmp8v7A .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZgkNKLFIDXmp8v7A .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZgkNKLFIDXmp8v7A .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZgkNKLFIDXmp8v7A .cluster text{fill:#333;}#mermaid-svg-ZgkNKLFIDXmp8v7A .cluster span{color:#333;}#mermaid-svg-ZgkNKLFIDXmp8v7A 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-ZgkNKLFIDXmp8v7A .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZgkNKLFIDXmp8v7A rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZgkNKLFIDXmp8v7A .icon-shape,#mermaid-svg-ZgkNKLFIDXmp8v7A .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZgkNKLFIDXmp8v7A .icon-shape p,#mermaid-svg-ZgkNKLFIDXmp8v7A .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZgkNKLFIDXmp8v7A .icon-shape .label rect,#mermaid-svg-ZgkNKLFIDXmp8v7A .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZgkNKLFIDXmp8v7A .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZgkNKLFIDXmp8v7A .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZgkNKLFIDXmp8v7A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
内存持续上涨 / OOM
jstat -gcutil 1s 看各区趋势
老年代是否只涨不落?
jcmd GC.class_histogram 找大对象
dump (-XX:+HeapDumpOnOutOfMemoryError)
MAT 分析: 找 GC Roots 到疑点的引用链
代码实战:死锁定位 (DeadlockDemo.java)------交叉持锁,然后用 ThreadMXBean.findDeadlockedThreads() 做编程式检测(与 jstack 的 "Found one Java-level deadlock" 结论一致):
java
Thread t1 = new Thread(() -> {
synchronized (LOCK_A) { sleep(100); synchronized (LOCK_B) { } }
}, "thread-A");
Thread t2 = new Thread(() -> {
synchronized (LOCK_B) { sleep(100); synchronized (LOCK_A) { } }
}, "thread-B");
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
long[] deadlocked = mxBean.findDeadlockedThreads();
for (ThreadInfo info : mxBean.getThreadInfo(deadlocked, true, true)) {
System.out.println("\"" + info.getThreadName() + "\" 状态: " + info.getThreadState());
System.out.println(" 正在等待锁: " + info.getLockName() + " (被线程 \"" + info.getLockOwnerName() + "\" 持有)");
for (StackTraceElement element : info.getStackTrace()) System.out.println(" at " + element);
}
实测输出:
text
=== 检测到死锁, 涉及 2 个线程 ===
"thread-A" 状态: BLOCKED
正在等待锁: java.lang.Object@179d3b25 (被线程 "thread-B" 持有)
at app//DeadlockDemo.lambda$main$0(DeadlockDemo.java:20)
...
"thread-B" 状态: BLOCKED
正在等待锁: java.lang.Object@3f91beef (被线程 "thread-A" 持有)
at app//DeadlockDemo.lambda$main$1(DeadlockDemo.java:29)
...
代码实战:堆 OOM 与自动 dump (HeapOomDemo.java):
bash
java -Xmx64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof HeapOomDemo
实测输出:
text
java.lang.OutOfMemoryError: Java heap space
Dumping heap to ./heapdump.hprof ...
Heap dump file created [35520573 bytes in 0.021 secs]
OOM 信息: Java heap space
崩溃前已持有 BigObject 数量: 31
7.4 调优原则
- 先测量,后优化:没有 GC 日志和监控基线之前,不动任何参数。大多数"感觉卡"最后定位到的是慢 SQL 或锁竞争,不是 GC。
- 一次只改一个参数,改完跑对照,否则无法归因。
- 默认值已经很好:G1 + 默认分代 + 压缩指针,对 4~32GB 堆的常规业务足够。
- 堆不是越大越好:堆越大单次 GC 停顿的绝对值可能越长(G1/ZGC 除外),且浪费内存成本。
- 调优的终点是代码:减少对象分配速率(复用、池化、避免大临时对象)比任何收集器参数都根本------4.6 节的实测(5 秒 38GB 分配)就是反例教材。
8. 附录
8.1 Demo 清单与运行方式
bash
cd jvm-demo
javac XxxDemo.java && java XxxDemo # 大部分 Demo
javac -d custom Hello.java # MyClassLoaderDemo 的前置编译
| 文件 | 演示内容 | 关键实测数据 |
|---|---|---|
ClassInitTimingDemo.java |
主动/被动引用 | 静态块执行时机 |
MyClassLoaderDemo.java |
双亲委派 + 类隔离 | c1 == c2 ? false |
StackDepthDemo.java |
栈深度 | 默认 22330 层 vs -Xss256k 2399 层 |
EscapeDemo.java |
逃逸分析 | 3ms/0GC vs 1050ms/414GC |
ReferenceTypeDemo.java |
四种引用 | 弱引用清除、虚引用入队 |
WeakHashMapDemo.java |
WeakHashMap 陷阱 | size 2 → 0 |
HeapOomDemo.java |
堆 OOM + dump | 35MB hprof 自动生成 |
MetaspaceOomDemo.java |
元空间 OOM | 约 2 万个类后 OutOfMemoryError: Metaspace |
GcLogDemo.java |
GC 日志制造机 | 5 秒 38GB / 674 次 YGC |
DeadlockDemo.java |
死锁与检测 | findDeadlockedThreads 命中 2 线程 |
VirtualThreadDemo.java |
虚拟线程 vs 线程池 | 268ms vs 7816ms |
PinningDemo.java |
JDK 21 synchronized pinning | 823ms vs 204ms + trace 证据 |
JitWarmupDemo.java |
JIT 分层编译 | tier 2 → tier 4 → made not entrant |
8.2 延伸阅读
- 《Java虚拟机规范》(Java SE 21 Edition)------所有行为的最终依据;
- 《深入理解 Java 虚拟机》(周志明,第三版)------中文 JVM 圣经;
- Oracle 官方 HotSpot GC 调优指南------GC 参数与收集器行为的一手资料;
- JDK 21 JEP 列表------重点:JEP 444(虚拟线程)、JEP 439(分代 ZGC)、JEP 446/453(Scoped Values / Structured Concurrency 预览);
- JMH 官方 Samples------写靠谱基准测试的唯一选择。
8.3 后续学习路径(按需选择)
- GC 深入:G1/ZGC 日志逐字段精读 + 调参实验(堆大小、停顿目标、并发线程数的耦合关系);
- JVM 排障实战:复现 CPU 飙高 / 内存泄漏 / 死锁三类 case,走完整「监控 → 定位 → 修复」链路;
- JIT 深入 :
-XX:+PrintInlining+ JITWatch 看字节码到汇编码的翻译,理解内联失败原因; - 类加载器进阶:手写 mini 热加载容器(自定义加载器 + 文件监听),理解 Tomcat/OSGi 设计;
- JDK 21 特性压测:虚拟线程替换线程池的改造实验 + 分代 ZGC 上线评估;
- 落地 genesis:把本文档的调参与监控方法用到 genesis 的 user/auth/gateway 压测上,先建 GC 日志基线再谈优化。
本文档所有代码在 OpenJDK 21.0.11 下实测通过;参数默认值以 HotSpot 21 为准,换 JDK 厂商(如 OpenJ9、GraalVM)时行为可能有差异。