JVM 技术指南:内存模型、垃圾回收与调优
字数
1394 字
阅读时间
6 分钟
1 JVM 内存模型
1.1 运行时数据区
1.1.1 堆(Heap)
- 存储对象实例和数组
- 所有线程共享
- 是垃圾收集器管理的主要区域
- 可以分为:
- 新生代(Young Generation)
- Eden 空间
- From Survivor 空间
- To Survivor 空间
- 老年代(Old Generation)
- 新生代(Young Generation)
1.1.2 方法区(Method Area)
- 存储类信息、常量、静态变量等
- JDK 8 之后使用元空间(Metaspace)替代永久代
- 所有线程共享
1.1.3 程序计数器(Program Counter Register)
- 当前线程执行字节码的行号指示器
- 线程私有
- 唯一一个不会发生 OutOfMemoryError 的内存区域
1.1.4 虚拟机栈(VM Stack)
- 存储方法执行时的局部变量、操作数栈等
- 线程私有
- 可能抛出 StackOverflowError 和 OutOfMemoryError
1.1.5 本地方法栈(Native Method Stack)
- 为本地方法(Native Method)服务
- 线程私有
1.2 对象创建过程
- 类加载检查
- 分配内存
- 初始化零值
- 设置对象头
- 执行构造方法
2 垃圾回收机制
2.1 判断对象存活的算法
2.1.1 引用计数法
- 给对象添加引用计数器
- 优点:实现简单,判断效率高
- 缺点:无法解决循环引用问题
2.1.2 可达性分析
- 从 GC Roots 开始搜索,不可达的对象被回收
- GC Roots 包括:
- 虚拟机栈中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
2.2 垃圾收集算法
2.2.1 标记-清除算法(Mark-Sweep)
- 标记需要回收的对象
- 统一回收被标记的对象
- 缺点:产生大量内存碎片
2.2.2 复制算法(Copying)
- 将内存分为两块,每次只使用其中一块
- 存活对象复制到另一块
- 优点:效率高,无碎片
- 缺点:内存利用率低
2.2.3 标记-整理算法(Mark-Compact)
- 标记后将存活对象向一端移动
- 清理边界以外的内存
- 适用于老年代
2.2.4 分代收集算法
- 新生代使用复制算法
- 老年代使用标记-整理或标记-清除算法
2.3 常见垃圾收集器
2.3.1 Serial 收集器
- 单线程收集器
- 简单高效
- 适用于客户端环境
2.3.2 ParNew 收集器
- Serial 的多线程版本
- 适用于服务器环境
2.3.3 CMS(Concurrent Mark Sweep)收集器
- 以最短停顿时间为目标
- 分四个步骤:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
2.3.4 G1 收集器
- 面向服务端应用的收集器
- 可预测的停顿时间模型
- 将堆划分为多个区域(Region)
- 优先回收价值最大的区域
3 JVM 调优技巧
3.1 常用调优参数
3.1.1 堆内存相关
bash
-Xms: 初始堆大小
-Xmx: 最大堆大小
-Xmn: 新生代大小
-XX:SurvivorRatio: Eden 区与 Survivor 区的比例
3.1.2 垃圾收集器相关
bash
-XX:+UseSerialGC: 使用 Serial + Serial Old 收集器
-XX:+UseParNewGC: 使用 ParNew + Serial Old 收集器
-XX:+UseConcMarkSweepGC: 使用 ParNew + CMS 收集器
-XX:+UseG1GC: 使用 G1 收集器
3.2 调优策略
3.2.1 内存调优
- 根据应用特点设置合适的堆大小
- 设置合理的新生代和老年代比例
- 避免内存泄漏
- 及时处理垃圾对象
3.2.2 GC 调优
- 选择合适的垃圾收集器
- 合理设置 GC 触发时机
- 控制 Full GC 的频率
- 优化对象生命周期
3.3 性能监控工具
3.3.1 命令行工具
- jps: 查看 Java 进程
- jstat: 监控 JVM 统计信息
- jmap: 生成堆转储文件
- jstack: 生成线程转储文件
3.3.2 可视化工具
- JConsole: Java 自带的监控工具
- VisualVM: 功能强大的故障诊断工具
- MAT: 内存分析工具
- JProfiler: 商业级性能分析工具
3.4 常见问题诊断
3.4.1 内存泄漏
- 症状:内存占用持续增长
- 诊断方法:
- 使用 jmap 生成堆转储文件
- 使用 MAT 分析内存泄漏
- 查找泄漏对象的引用链
3.4.2 性能问题
- 症状:响应时间变慢,CPU 使用率高
- 诊断方法:
- 使用 jstack 查看线程状态
- 分析 GC 日志
- 检查系统资源使用情况
4 最佳实践
4.1 开发建议
- 合理使用对象池和缓存
- 注意对象生命周期管理
- 避免创建过多临时对象
- 及时释放不用的对象引用
4.2 运维建议
- 定期监控 JVM 状态
- 设置合理的告警阈值
- 制定清晰的问题处理流程
- 保持系统运行日志的完整性
5 总结
JVM 是 Java 平台的核心组件,理解其内存模型、垃圾回收机制和调优技巧对于开发高性能 Java 应用至关重要。通过合理的配置和持续的监控优化,可以使应用程序在生产环境中保持良好的性能和稳定性。