Java 后端手册JAVA BACKEND

CHAPTER 02运行时 · JVM

JVM 原理

内存区域、垃圾回收、类加载、JMM 内存模型与基础调优。理解 JVM 才能写出内存友好、可诊断的服务。

开始阅读难度进阶先修第一章约3小时
02.1

内存区域

JVM 把内存划分为:堆(对象实例)、虚拟机栈(方法帧/局部变量)、元空间(类元数据)、程序计数器等。OOM 往往来自堆或元空间。

区域存什么溢出错误
堆 Heap对象实例OutOfMemoryError: Java heap space
虚拟机栈方法调用帧StackOverflowError
元空间 Metaspace类元数据Metaspace OOM
02.2

堆与栈

基本类型与对象引用在栈上;对象实例在堆上。方法结束栈帧出栈,对象等 GC 回收。理解这点能解释"参数为何是值传递"。

void f(User u) { u = new User(); }  // 仅改变局部引用副本,调用方不变
void g(User u) { u.setName("x"); }  // 修改同一堆对象,调用方可见
Java 只有值传递

传递对象时复制的是"引用的值",所以能改对象状态,但无法让调用方的变量指向新对象。

02.3

垃圾回收

GC 通过"可达性分析"从 GC Roots 出发标记存活对象,回收不可达者。堆分代:新生代(Eden+Survivor)与老年代,采用不同策略。

新对象 → Eden
        频繁 Minor GC(复制存活对象到 Survivor)
        熬过多次 → 老年代
老年代满 → Major/Full GC(更慢)
减少大对象

大对象直接进入老年代,易触发 Full GC 停顿。缓存、大集合要控制生命周期,避免无谓晋升。

02.4

常见收集器

G1 是默认平衡选择;ZGC/Shenandoah 追求亚毫秒级停顿,适合大堆低延迟。选收集器要权衡吞吐与停顿。

收集器特点适用
G1区域化、可预测停顿通用默认
ZGC并发、<10ms 停顿大堆低延迟
Serial/Parallel吞吐优先小应用/批处理
02.5

类加载

类加载分加载→链接(验证/准备/解析)→初始化。双亲委派:先让父加载器尝试,保证核心类不被篡改、全局唯一。

Bootstrap → Platform(Ext) → Application → 自定义
(子加载器优先委托父加载器)
02.6

JMM 内存模型

JMM 定义多线程下变量的可见性与有序性。普通变量修改对其他线程未必立即可见;volatile 保证可见性与禁止重排(不保证原子性)。

private volatile boolean running = true;
// 一个线程改 running=false,另一线程循环读能立即看到
volatile 非万能

i++ 这种"读-改-写"即使 volatile 也非原子,需用 AtomicInteger 或锁。

02.7

基础调优

通过 JVM 参数设定堆大小与 GC 日志,结合监控定位瓶颈。盲目调大堆可能反而延长 GC 停顿。

java -Xms2g -Xmx2g -XX:+UseG1GC \
     -Xlog:gc*:file=gc.log:time \
     -jar app.jar
先观测后调优

用 GC 日志与 APM 找出真实瓶颈(是停顿、内存还是 CPU?)再动手,别凭感觉改参数。

02.8

常见陷阱与 FAQ

内存泄漏在 Java 还存在吗?

存在——通常是"本该释放的引用被长期持有"(如静态 Map、未注销的监听器、线程池里的 ThreadLocal 没 remove)。GC 只回收不可达对象。

Full GC 频繁怎么查?

看 GC 日志判断是老年代增长快还是元空间满;用堆转储(heap dump)+ MAT 找大对象与引用链。

为什么局部变量不需要 volatile?

局部变量在栈上,线程私有,不存在跨线程可见性问题,所以无需 volatile。

02.9

实战练习与资源

延伸资源