19_Java JVM与内存
类加载、堆栈与元空间、垃圾回收、内存泄漏和性能排查顺序。
Java JVM 与内存
学习目标:建立类加载、堆、栈、垃圾回收和性能诊断的基本模型。
1. 从源码到运行
javac 把 Java 源码编译成字节码,JVM 加载类并执行。运行时可结合解释执行与即时编译(JIT)优化热点代码。JVM 并不等于某一种固定的垃圾回收器,具体行为受版本和配置影响。
2. 内存区域的实用模型
| 区域 | 主要用途 | 常见问题 |
|---|---|---|
| 堆 | 对象实例与数组 | 对象存活过久、分配压力 |
| 线程栈 | 方法调用帧、局部变量等 | 递归过深导致栈溢出 |
| 元空间 | 类元数据 | 类加载器泄漏等 |
这是帮助排查问题的模型,不应把“所有对象都一定在堆上”当成优化定律;JIT 可能进行逃逸分析等优化。
3. 垃圾回收与泄漏
垃圾回收(GC)主要回收不再可达的对象。Java 仍会发生内存泄漏:静态集合、缓存、监听器或线程局部变量长期持有本应释放的对象。频繁 GC 可能是症状,应结合分配速率、堆占用、暂停时间和业务流量判断。
易错点:不要把每次 `OutOfMemoryError` 都归因于“堆太小”。堆、元空间、直接内存、线程数量及容器内存限制都可能相关。
4. 排查顺序
- 先看错误率、延迟、CPU、内存和 GC 指标,确认时间点与影响范围。
- 收集线程转储、堆转储或 JFR 记录;生产环境操作要评估体积和性能影响。
- 找出持续增长的对象类型和引用路径,再定位持有者与业务代码。
- 优先修正无界缓存、未关闭资源或不合理并发,最后再根据量测调 JVM 参数。
Tip:把“变慢前后”的指标并排看,比孤立地盯住一次 GC 日志更容易判断原因。
自测
- Java 有 GC 就一定不会内存泄漏吗?不是,仍可能长期持有不再需要的对象。
- 为什么调大堆不是所有内存问题的第一步?可能掩盖无界增长,且问题也可能不在堆。
5. 区分三类常见故障
StackOverflowError 常见于无限递归或过深调用;OutOfMemoryError: Java heap space 可能是堆太小,也可能是缓存或集合无界增长;进程被容器终止还可能因为总内存超限,包含堆外内存、线程栈等。先记录错误信息和进程内存曲线,再决定收集什么证据。
排查顺序:确认最近发布与流量变化 → 看 GC 频率、暂停和堆使用趋势 → 查看线程数量与 CPU → 在合适环境采集 JFR、线程转储或堆转储 → 根据引用链定位代码。堆转储可能包含用户数据,保存与分享时要遵守访问控制。
6. GC 指标如何读
GC 回收的是不可达对象;暂停时间、吞吐量和内存占用之间有权衡。一次高堆占用不等于泄漏:缓存预热和批处理也可能短时增高。若多轮 GC 后存活对象基线持续上升,才更值得追查长期引用。先修复无界队列、缓存和未关闭资源,再尝试调节堆与收集器参数。
练习:写一个持续把对象加入静态 List 的小程序,观察堆曲线;清空引用后再比较。