《Java 虚拟机原理与性能调优》 · 全书目录与章节索引(31 篇)
《Java 虚拟机原理与性能调优》全 31 篇,承接卷三 libraryd,用 jcmd、JFR、GC 日志与堆转储讲清类加载、对象布局、JIT、垃圾回收、内存诊断、线程锁运行时、启动加速与综合压测调优:10 章 30 节加目录的完整索引。
tag
《Java 虚拟机原理与性能调优》全 31 篇,承接卷三 libraryd,用 jcmd、JFR、GC 日志与堆转储讲清类加载、对象布局、JIT、垃圾回收、内存诊断、线程锁运行时、启动加速与综合压测调优:10 章 30 节加目录的完整索引。
《Java 虚拟机原理与性能调优》全 31 篇的目录与学习路径:10 章 30 节,从研究方法、类文件与对象布局,到 JIT、GC、内存诊断、线程与锁、启动加速和综合压测调优,给出三条按症状跳读的路线、统一实验口径、项目状态、机器口径与各卷衔接。
把启动、吞吐、p50/p95/p99、GC 次数与停顿、堆占用写成一份基线 JSON,用 scripts/jvm/baseline.py 采集并对比。本节真跑两次基线得到 PASS,再用 -Xint 制造回归得到 FAIL,并用 Little 定律和 p99 曲线给出 libraryd 的容量表与扩容触发条件。
报告端点 p99 40 到 57ms,团队第一反应是加 -Xms2g -Xmx2g -XX:MaxGCPauseMillis=50。本节按现象、证据、假设、改动、回归走一遍,实测三组参数与并发曲线,结论是 GC 停顿只占 p99 的 5%,真正有效的是限制单实例在途请求。
同样 5000 个请求、50 并发,ab 不带 -k 是 1909 req/s、p99 78ms,带 -k 是 10201 req/s、p99 17ms。本节用这两份真实输出讲吞吐、均值、分位、连接复用与协调缺失,并用 Little 定律和开环压测器说明为什么并发不是越高越好。
JDK 25 的 AOT 缓存分 record、create、run 三阶段,缓存类链接结果和方法 profile,不是机器码。本节实测归档大小、目录 classpath 的失败、create 阶段漏传 classpath 的报错与 jar 变更后的降级行为,并把 AOT 缓存、AppCDS 与 GraalVM Native Image 的适用边界分开。
默认 CDS 已经把 JDK 核心类放进共享归档,AppCDS 只负责应用类。本节用 -Xshare:dump、ArchiveClassesAtExit、SharedArchiveFile 和 jcmd VM.cds 实测归档大小与启动差异,并给出目录 classpath 失败、相对路径失效和 -Xshare:on 硬失败的处理方式。
把 libraryd 的启动拆成进程启动、VM 初始化、类加载、main 返回和首请求五个时间点。本节用 startuptime、class+load、JFR ClassLoad 与十次重复测量,给出 Hello 与最小 HTTP 服务的真实分布,并解释 readiness 通过为什么不等于首请求就绪。
1000 个虚拟线程只对应 2 个载体,这是怎么做到的?本节用 Thread.vthread_scheduler、JSON 线程容器和 pin 心跳实验拆开 Continuation、mount/unmount 与调度,验证 JDK 25 的 synchronized 不再 pin,而本地调用和类初始化仍会占住载体。
JDK 25 已经没有偏向锁。本节用 PrintFlagsFinal 确认 UseBiasedLocking 消失,用 javap 看 synchronized 方法与方法块的字节码差异,再用 JFR 记录 JavaMonitorEnter、Wait 与 Inflate,对比 synchronized 和 ReentrantLock 的真实竞争事件。
jstack 看不到虚拟线程。本节用 1000 个虚拟线程加 4 个平台工作线程实测 Thread.print 与 Thread.dump_to_file,读出 JSON 里的 root、ThreadPerTaskExecutor 与 ForkJoinPool 容器,并给出死锁检测、线程状态判断和 libraryd 的转储脚本。
堆没满、进程却一直涨,问题常在 -Xmx 之外。本节用 Byte Buddy 1.18.16 生成类,在 64 MB 元空间上限下复现真实 Metaspace OOM;再用 32 MB 直接内存上限复现 Cannot reserve 报错,并给出 VM.metaspace、NMT 与插件热重载的排查路径。
内存一直涨不等于泄漏。本节用 -Xmx64m 把一个 static Map 逼到真实 OutOfMemoryError,采样出 13 MB 到 67 MB 的线性增长曲线,用直方图盯住 [B 的实例与字节,并用 WeakHashMap 实验说明弱引用为什么救不了值反向持有键的缓存。
堆转储不是截一张内存快照就完事。本节用 500 个 64 KB 数组实测 jcmd GC.heap_dump 生成 37 MB hprof,讲清 shallow size、retained size 与支配树,给出 GC Root 路径的读法、MAT 操作步骤和 libraryd 的 dump 落盘策略。
ZGC 的停顿为什么和堆大小基本无关?染色指针与读屏障到底做了什么?本节用本机 10 秒 G1 与 ZGC 对照实验拆开停顿、MMU、Allocation Stall 与 RSS,并说明换收集器前要算清的三笔账,读完能判断什么时候该换、换了要付什么代价。
G1 的 region 到底多大?多大的对象算 Humongous?IHOP 和 MaxGCPauseMillis 改了为什么没效果?本节用本机实测拆开 Region、Humongous、Mixed GC 与停顿目标,并给出 libraryd 导出任务避开大对象的写法。
一行 GC(9) Pause Young (Mixed) 431M->313M(512M) 0.964ms 里每个字段是什么意思?本节用本机 12 秒真实日志拆字段、讲标签、复现 Evacuation Failure,并给出解析停顿的 gc-summary.py。
五种收集器在同一台 M1 Pro、同一份负载下的真实对照:Serial 与 Parallel 的日志形状、G1 的 region、ZGC 的 0.053ms 最大停顿和 Shenandoah 的 256K region,附选型表与可复现命令。
同样一段分配负载,Serial 打印 DefNew/Tenured,Parallel 打印 PSYoungGen/ParOldGen,G1 打印的是一串 region 计数。本节用三份真实 GC 日志拆开分代假设、三种回收算法与晋升年龄,并在 -Xmx64m 下量出年龄表最高只到 age 6。
libraryd 的 LoanCache 清了引用还占着 1.2 GB。本节用 GC Root 实验、弱/软/虚引用与 Cleaner 的真实输出,量出 -Xmx64m 下软引用只剩 16/4096,并解释 WeakHashMap 为什么留不住条目。