JVM 内存与 GC 概览
运行时数据区直觉、堆上对象与 GC 基本目标:先正确测量再谈调优,并标注实现相关性。
#concept
#growing
#dev
#java
[!info] related notes
JVM 内存与 GC 概览
这个概念为什么出现
OOM、频繁停顿、堆配置争论都指向内存与 GC。先建立对象在堆、帧在栈、GC 回收不可达对象的模型,再谈收集器名称与参数。
[!abstract] 一句话理解 对象主要在堆分配;GC 回收不可达对象;具体分代/收集器/停顿行为高度实现相关,必须测量后调优。
最小可观察示例
场景:分配大量临时对象观察程序仍可结束(示意)
public class AllocChurn {
public static void main(String[] args) {
long sum = 0;
for (int i = 0; i < 100_000; i++) {
sum += ("id-" + i).length(); // 源码产生临时字符串,实际分配可能被 JIT 优化
}
System.out.println(sum);
}
}
javac AllocChurn.java && java -Xms32m -Xmx32m AllocChurn
在 JDK 21 中期望打印 788890 并正常退出。HotSpot 可用完整命令 java -Xms32m -Xmx32m -Xlog:gc*:file=gc.log AllocChurn 观察日志。这个短程序不保证触发 GC;正常退出也不能证明每个临时对象都实际分配过或已被回收。
结合场景再看三个关注点
- 临时对象靠 GC 回收,不是手动 free。
-Xmx限制 Java 堆,不等于 JVM 进程或容器总内存上限。- 检查日志中的具体 GC 事件;只有初始化信息不代表发生过回收,有 GC 也不代表需要立刻调参。
核心概念与准确模型
1. 运行时数据区(教学模型)
- 堆:对象实例
- JVM 栈:调用帧、局部变量与操作数栈;局部变量可含基本值或对象引用。
- 方法区是 JVMS 的逻辑区域;HotSpot 元空间主要承载类元数据,使用本地内存,不能把两者当作完全同义词。
- PC/本地栈等
2. 可达性
从 GC roots 不再强可达的对象才可能进入后续引用处理/回收流程;弱、软、虚引用另有语义。可回收不等于立即回收,GC 也不负责及时关闭文件、连接或锁。JIT 可能通过逃逸分析和标量替换消除分配,教学数据区模型不规定每个值的物理位置。
3. 收集器
Serial/Parallel/G1/ZGC… 名称与默认随 JDK 版本变。
边界情况与反直觉行为
- 内存泄漏常是“仍可达但不该可达”(缓存、监听器)。
- 堆外内存(直接内存、JNI)不在同一仪表盘。
- finalization 自 JDK 18 起已标记待移除,不保证及时执行;资源关闭应使用明确生命周期和 try-with-resources。
常见误区
[!warning] 常见误区:一上来抄 GC 参数大全 先修分配与泄漏,再用指标驱动。
工程实践
- 为容器预算堆、线程栈、元空间、代码缓存、直接内存和其他本地开销,不能把全部容器限额都给 Xmx。
- 观察分配速率、停顿、堆占用。
- 用 MAT/jcmd/jfr 定位保留路径。
- 版本升级后重测默认收集器变化。
本节总结
- 堆对象 + 可达性回收
- 实现相关细节要标注
- 测量优先于调参
自测题
- 栈上装的是对象本身吗?
- 为什么“泄漏”在 GC 语言里仍可能发生?
参考答案
- 栈帧持有引用/基本类型局部量;对象实例通常在堆。
- 对象仍被无意引用保持可达,GC 无法收。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| HotSpot GC Tuning Guide | 官方 | 实现向导 |
| JVMS Runtime Data Areas | 规范 | 数据区概念 |