← Backend / Java

19_Java JVM与内存

类加载、堆栈与元空间、垃圾回收、内存泄漏和性能排查顺序。

Java JVM 与内存

学习目标:建立类加载、堆、栈、垃圾回收和性能诊断的基本模型。

1. 从源码到运行

javac 把 Java 源码编译成字节码,JVM 加载类并执行。运行时可结合解释执行与即时编译(JIT)优化热点代码。JVM 并不等于某一种固定的垃圾回收器,具体行为受版本和配置影响。

2. 内存区域的实用模型

区域 主要用途 常见问题
堆 对象实例与数组 对象存活过久、分配压力
线程栈 方法调用帧、局部变量等 递归过深导致栈溢出
元空间 类元数据 类加载器泄漏等

这是帮助排查问题的模型,不应把“所有对象都一定在堆上”当成优化定律;JIT 可能进行逃逸分析等优化。

3. 垃圾回收与泄漏

垃圾回收(GC)主要回收不再可达的对象。Java 仍会发生内存泄漏:静态集合、缓存、监听器或线程局部变量长期持有本应释放的对象。频繁 GC 可能是症状,应结合分配速率、堆占用、暂停时间和业务流量判断。

易错点:不要把每次 `OutOfMemoryError` 都归因于“堆太小”。堆、元空间、直接内存、线程数量及容器内存限制都可能相关。

4. 排查顺序

  1. 先看错误率、延迟、CPU、内存和 GC 指标,确认时间点与影响范围。
  2. 收集线程转储、堆转储或 JFR 记录;生产环境操作要评估体积和性能影响。
  3. 找出持续增长的对象类型和引用路径,再定位持有者与业务代码。
  4. 优先修正无界缓存、未关闭资源或不合理并发,最后再根据量测调 JVM 参数。
Tip:把“变慢前后”的指标并排看,比孤立地盯住一次 GC 日志更容易判断原因。

自测

  1. Java 有 GC 就一定不会内存泄漏吗?不是,仍可能长期持有不再需要的对象。
  2. 为什么调大堆不是所有内存问题的第一步?可能掩盖无界增长,且问题也可能不在堆。

5. 区分三类常见故障

StackOverflowError 常见于无限递归或过深调用;OutOfMemoryError: Java heap space 可能是堆太小,也可能是缓存或集合无界增长;进程被容器终止还可能因为总内存超限,包含堆外内存、线程栈等。先记录错误信息和进程内存曲线,再决定收集什么证据。

排查顺序:确认最近发布与流量变化 → 看 GC 频率、暂停和堆使用趋势 → 查看线程数量与 CPU → 在合适环境采集 JFR、线程转储或堆转储 → 根据引用链定位代码。堆转储可能包含用户数据,保存与分享时要遵守访问控制。

6. GC 指标如何读

GC 回收的是不可达对象;暂停时间、吞吐量和内存占用之间有权衡。一次高堆占用不等于泄漏:缓存预热和批处理也可能短时增高。若多轮 GC 后存活对象基线持续上升,才更值得追查长期引用。先修复无界队列、缓存和未关闭资源,再尝试调节堆与收集器参数。

练习:写一个持续把对象加入静态 List 的小程序,观察堆曲线;清空引用后再比较。