JVM学习文档

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 的编辑器中可直接渲染

目录

  1. [JVM 整体架构](#JVM 整体架构)
  2. 类加载机制
  3. 运行时数据区
  4. 垃圾回收
  5. [JIT 编译与执行引擎](#JIT 编译与执行引擎)
  6. [线程模型与虚拟线程(JDK 21)](#线程模型与虚拟线程(JDK 21))
  7. 性能监控与调优
  8. 附录

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 "一次编译,到处运行"的真相

  1. 跨平台的是字节码,不是 Java 源码,也不是 JVM 行为本身。
  2. 字节码被 JIT 编译成本地机器码后,产物与操作系统、CPU 架构绑定。
  3. 不同 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 打破双亲委派的三大场景

  1. SPI 机制 :DriverManager 由 Bootstrap 加载,MySQL 驱动在 App 层。Bootstrap 反过来需要加载 App 层的类,只能通过 Thread.currentThread().getContextClassLoader()(线程上下文类加载器)拿到 App 加载器。
  2. Tomcat :每个 Web 应用一个类加载器,实现应用间隔离、热部署;javax.servlet 等容器类则向上委派。
  3. OSGi / 热部署框架:网状委派,程序自己控制加载边界。

2.4 主动引用 vs 被动引用:初始化时机

主动引用(必然触发初始化):

  1. new 实例化、读写静态字段(非 final 常量)、调用静态方法;
  2. 反射调用(Class.forName);
  3. 初始化子类会先触发父类初始化;
  4. 启动类(main 所在类)。

被动引用(不触发初始化):

  1. 子类访问父类的静态字段 → 只初始化父类;
  2. 定义对象数组(new Child[3]);
  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

要点:

  1. 只重写 findClass,不要重写 loadClass ------loadClass 里封装了双亲委派逻辑,重写它等于亲手打破模型;
  2. 用 defineClass(name, bytes, 0, len) 把字节数组定义为类;
  3. 同一个全限定名被不同加载器加载,是不同的类 (== 为 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)

  1. 每个线程一份,记录当前线程正在执行的字节码指令地址(行号)。
  2. 执行 native 方法时值为空(undefined)。
  3. 它是唯一在 JVM 规范中没有规定 OOM 情况的区域------因为只是记个行号,没有存储压力。

3.3 虚拟机栈与栈帧

**栈帧(Stack Frame)**四要素:

  1. 局部变量表 :方法参数、局部变量,以槽(slot)计,long/double 占 2 槽;
  2. 操作数栈 :字节码指令的工作区(如 iadd 从栈顶取两个数相加);
  3. 返回地址:方法正常/异常返回后回到调用处;
  4. 动态链接:指向运行时常量池中该方法的引用,支撑多态(运行期才知道调用哪个实现)。

代码实战:栈深度与 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 本地方法栈

  1. 为 JNI(Java Native Interface)调用本地方法服务。
  2. HotSpot 中本地方法栈与虚拟机栈合二为一,-Xss 同时约束两者。
  3. 本地方法里再回调 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

  1. TLAB :每个线程在 Eden 中预分配一小块私有缓冲区,new 时指针碰撞分配,无线程竞争(-XX:+UseTLAB,默认开启)。
  2. 新生代 :Eden : Survivor 默认 8 : 1 : 1(-XX:SurvivorRatio=8),每次 Young GC 存活对象在 S0/S1 之间拷贝轮换。
  3. 晋升老年代 两条路径:年龄熬到 15;或 Survivor 中同年龄对象总和超过 Survivor 一半,这些对象提前晋升。
  4. 动态分代只是概念: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 默认无上限,受本机内存约束
  1. 方法区存:类元数据(类型信息、字段/方法描述、字节码)、运行时常量池、JIT 编译产物缓存等。
  2. JDK 8 起压缩类指针(UseCompressedClassPointers)开启时,类数据还有一块独立的 Compressed Class Space ,默认上限 1GB(-XX:CompressedClassSpaceSize)。它和 MaxMetaspaceSize 是两道独立的闸门,任何一道触顶都 OOM。
  3. 字符串常量池(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 字节
  1. 压缩指针(-XX:+UseCompressedOops)默认开启,此时类型指针 4 字节,堆 ≤ 32GB 均可受益;超过 32GB 自动关闭。
  2. 数组对象头多一个 4 字节的 length 字段。
  3. Mark Word 是复用的:无锁态存 hashcode,加锁后依次升级为轻量级锁/重量级锁标志,GC 时存年龄与标记位。

⚠️ 实战陷阱 :-XX:+PrintFieldLayout 需要 JOL 依赖(org.openjdk.jol:jol-core)才能看每字段偏移;生产排查对象布局一律用 JOL,不要凭"int 4 字节 long 8 字节"硬算------字段重排后真实布局会出乎意料(如 boolean + int 组合的填充)。

3.8 逃逸分析

逃逸分两种:

  1. 方法逃逸:对象作为返回值/参数传给外部方法;
  2. 线程逃逸:对象被其他线程访问(如赋值给静态字段、放进共享集合)。

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 包括:

  1. 虚拟机栈中局部变量表引用的对象;
  2. 方法区中静态属性、常量引用的对象;
  3. 本地方法栈中 JNI 引用的对象;
  4. 虚拟机内部引用(常驻异常、系统类加载器等);
  5. 被同步锁(synchronized)持有的对象;
  6. 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 与安全点

  1. Stop-The-World(STW) :GC 的某些阶段必须暂停所有 Java 线程,保证对象引用关系不变。GC 调优的本质就是压缩 STW 总时长与频率。
  2. 安全点(SafePoint):线程不是想停就停,只能停在安全点(方法调用前、循环跳转处、异常抛出等)。线程跑到安全点附近才会响应"GC 邀请"。
  3. 安全区(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 停顿 短生命周期任务/性能测试基线 实验性

选择建议:

  1. 默认 G1 不动,90% 的应用不需要换收集器;
  2. 堆 ≥ 16GB 且停顿敏感 → 试 ZGC(-XX:+UseZGC -XX:+ZGenerational,JDK 21 分代版);
  3. 纯吞吐优先(离线报表、大数据批处理)→ Parallel;
  4. 调优前先有数据(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 实战陷阱汇总

  1. System.gc() 是定时炸弹 :生产代码里调一次就是一次 Full GC。更隐蔽的是 RMI 的分布式 GC 会周期性触发 System.gc()------关掉它:-XX:+DisableExplicitGC。
  2. finalize() 已废弃 :JDK 9 起废弃、JDK 18 起标记"即将移除",JDK 21 仍能跑但随时可能消失。对象复活/清理逻辑请改用 Cleaner 或 PhantomReference。
  3. CMS 的遗产 :老系统还有 -XX:+UseConcMarkSweepGC 参数,JDK 21 直接启动报错,迁移时改成 G1 或 ZGC。
  4. 停顿目标别拍脑袋 :-XX:MaxGCPauseMillis=10 对 64GB 堆可能逼着 G1 频繁小批回收,吞吐崩掉。停顿目标是"努力方向",不是合同。
  5. 伪共享(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

解读:

  1. 110 56 2 ... compute:第 2 层(C1)编译了 compute;
  2. 116 58 4 ... compute:升到第 4 层(C2)重新编译;
  3. 117 56 2 ... made not entrant:旧的 C1 版本被标记为不再进入(被 C2 版取代)。

5.3 热点探测

  1. 方法调用计数器 :方法被调用的次数,经典阈值 -XX:CompileThreshold=10000(分层编译下改由各 Tier 阈值控制);
  2. 回边计数器:循环执行次数,循环体热点也能触发编译;
  3. 热度会随时间衰减(半衰期机制),防止一次性的热点长期霸占编译资源。

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):

  1. 虚拟线程由 JVM 调度,跑在少量**载体线程(carrier)**上,载体默认是并行度 = CPU 核数的 ForkJoinPool;
  2. 虚拟线程阻塞(IO、sleep)时从载体卸载,让出载体给其他虚拟线程------阻塞不再消耗线程资源;
  3. 每任务一线程 成为新范式: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(线程安全视角)

  1. 每个虚拟线程可以有自己的 ThreadLocal (存在线程对象里,不在栈上),100 万个虚拟线程各挂一个 ThreadLocal = 内存灾难。池化时代"借线程还线程"的清理惯性(finally 里 remove())在虚拟线程时代依然要保留。
  2. JDK 21 预览了两个更好的替代:Scoped Values (JEP 446,不可变、按作用域继承)和 Structured Concurrency(JEP 453,任务生命周期与作用域绑定)。
  3. 虚拟线程不是为了提速 CPU 密集型任务------计算密集场景用平台线程 + 合理并行度即可。
  4. 平台线程的 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 调优原则

  1. 先测量,后优化:没有 GC 日志和监控基线之前,不动任何参数。大多数"感觉卡"最后定位到的是慢 SQL 或锁竞争,不是 GC。
  2. 一次只改一个参数,改完跑对照,否则无法归因。
  3. 默认值已经很好:G1 + 默认分代 + 压缩指针,对 4~32GB 堆的常规业务足够。
  4. 堆不是越大越好:堆越大单次 GC 停顿的绝对值可能越长(G1/ZGC 除外),且浪费内存成本。
  5. 调优的终点是代码:减少对象分配速率(复用、池化、避免大临时对象)比任何收集器参数都根本------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 延伸阅读

  1. 《Java虚拟机规范》(Java SE 21 Edition)------所有行为的最终依据;
  2. 《深入理解 Java 虚拟机》(周志明,第三版)------中文 JVM 圣经;
  3. Oracle 官方 HotSpot GC 调优指南------GC 参数与收集器行为的一手资料;
  4. JDK 21 JEP 列表------重点:JEP 444(虚拟线程)、JEP 439(分代 ZGC)、JEP 446/453(Scoped Values / Structured Concurrency 预览);
  5. JMH 官方 Samples------写靠谱基准测试的唯一选择。

8.3 后续学习路径(按需选择)

  1. GC 深入:G1/ZGC 日志逐字段精读 + 调参实验(堆大小、停顿目标、并发线程数的耦合关系);
  2. JVM 排障实战:复现 CPU 飙高 / 内存泄漏 / 死锁三类 case,走完整「监控 → 定位 → 修复」链路;
  3. JIT 深入 :-XX:+PrintInlining + JITWatch 看字节码到汇编码的翻译,理解内联失败原因;
  4. 类加载器进阶:手写 mini 热加载容器(自定义加载器 + 文件监听),理解 Tomcat/OSGi 设计;
  5. JDK 21 特性压测:虚拟线程替换线程池的改造实验 + 分代 ZGC 上线评估;
  6. 落地 genesis:把本文档的调参与监控方法用到 genesis 的 user/auth/gateway 压测上,先建 GC 日志基线再谈优化。

本文档所有代码在 OpenJDK 21.0.11 下实测通过;参数默认值以 HotSpot 21 为准,换 JDK 厂商(如 OpenJ9、GraalVM)时行为可能有差异。

相关推荐
无心水2 小时前
15、云原生架构设计理论与实践 核心考点与学习笔记
笔记·学习·云原生·架构师·软考·系统架构设计师·软考系统架构设计师
动词ing2 小时前
【学习笔记】51单片机 独立按键控制LED显示二进制
笔记·学习·51单片机
叼烟扛炮3 小时前
Linux 学习笔记(三十九):网络基石——TCP/IP 模型与 UDP 实战
linux·运维·网络·笔记·学习·tcp/ip
wuminyu3 小时前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
EVEN-09133 小时前
Linux-线程控制
linux·jvm
前进的李工3 小时前
Agent 学习笔记 04:KV Cache 与上下文缓存
笔记·学习·缓存·agent·注意力机制·kv cache·prompt cache
Z21-X3 小时前
云原生运维笔记:打通OpenStack的任督二脉——从Neutron网络踩坑到Swift与Heat硬核实战
运维·笔记·学习·云原生
旖旎夜光3 小时前
LCR 170:交易逆序对的总数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
小向是个Der12 小时前
嵌入式进阶——MCU上电后的动作
c语言·单片机·mcu·学习·链接脚本