一、JVM 整体架构概览
JVM 本质:Java 字节码的运行容器 + 内存管理 + 线程调度 + 垃圾回收。
核心组成:
- 类加载子系统:加载 .class 文件
- 运行时数据区(内存结构)
- 执行引擎:解释 / 编译执行字节码
- 本地方法接口:调用 OS/C++ 本地方法
- 垃圾回收器:自动内存管理
二、运行时数据区(JVM 内存结构)
1. 线程私有区域(每个线程独立)
(1)程序计数器(PC Register)
- 保存当前线程执行的字节码行号
- 线程切换后能恢复执行位置
- 唯一不会 OOM 的区域
(2)虚拟机栈(VM Stack)
- 每个方法执行对应一个栈帧入栈
- 栈帧存储:局部变量表、操作数栈、动态链接、方法出口
- 异常:
StackOverflowError:栈深度溢出(递归死循环)OutOfMemoryError:栈扩展失败
(3)本地方法栈(Native Method Stack)
- 为
native方法服务 - 逻辑同虚拟机栈
2. 线程共享区域
(1)堆(Heap)------ JVM 核心
- 唯一目的:存放对象实例、数组
- GC 唯一管理的区域
- 可通过
-Xms-Xmx控制大小 - 异常:
java.lang.OutOfMemoryError: Java heap space
(2)方法区(Method Area)
- 存储:类信息、常量、静态变量、即时编译代码
- JDK8 以前:永久代(PermGen)
- JDK8 及以后:元空间(Metaspace) ,使用本地内存
- 异常:
OutOfMemoryError: Metaspace
(3)运行时常量池
- 属于方法区一部分
- 存放编译期生成的字面量、符号引用
- 运行期也可加入常量(如
String.intern())
三、堆内存分代模型(重点)
plaintext
Heap
├─ 年轻代 Young Gen(约 1/3)
│ ├─ Eden 区
│ ├─ Survivor From(S0)
│ └─ Survivor To(S1)
│
└─ 老年代 Old Gen(约 2/3)
1. 为什么分代?
对象生命周期差异大:
- 大部分对象朝生夕死 → 放年轻代
- 长期存活 → 老年代分代 = GC 效率最大化
2. 各区职责
- Eden:新对象出生地
- Survivor:对象缓冲、避免过早进入老年代
- Old:存活时间长的对象
四、对象完整生命周期(从创建到 GC)
- 新对象 → 优先分配 Eden
- Eden 满 → 触发 Minor GC
- 存活对象复制到 空 Survivor,年龄 +1
- 对象在 S0 ↔ S1 之间轮换
- 满足条件 → 晋升老年代
- 老年代满 → Major/Full GC
- 对象无引用 → 被回收
对象晋升老年代的条件
- 年龄达到阈值(默认 15)
- 大对象直接进入老年代
- Survivor 相同年龄对象总大小 > 一半空间
- Minor GC 后存活对象放不下 Survivor
五、垃圾回收机制
1. 如何判断对象可回收?
(1)引用计数法
- 简单,但无法解决循环引用
- JVM 不采用
(2)可达性分析算法(JVM 采用)
- GC Roots 作为起点向下搜索
- 不可达对象判定为垃圾
GC Roots 包括:
- 虚拟机栈中引用的对象
- 本地方法栈引用的对象
- 类静态属性引用的对象
- 常量引用的对象
2. 垃圾回收算法
(1)标记 - 清除
- 先标记,再清除
- 缺点:内存碎片严重
(2)复制算法
- 内存分两半,只使用一半
- 存活对象复制到另一半,清空原空间
- 优点:无碎片、速度快
- 适用:年轻代
(3)标记 - 整理
- 标记后让存活对象向一端移动
- 清除端外内存
- 适用:老年代
六、常见垃圾回收器
1. 年轻代回收器
- Serial:单线程,简单
- ParNew:Serial 多线程版
- Parallel Scavenge :注重吞吐量
2. 老年代回收器
- Serial Old:Serial 老年代版
- Parallel Old:注重吞吐量
- CMS :注重低延迟,并发回收
3. 全堆回收器
- G1:面向服务端,低延迟 + 高吞吐平衡
- ZGC/Shenandoah:超低延迟,JDK11+ 主流
组合常用搭配
- Serial + Serial Old
- ParNew + CMS
- Parallel Scavenge + Parallel Old
- G1(现代服务端首选)
七、类加载机制
1. 类加载过程
加载 → 验证 → 准备 → 解析 → 初始化
2. 三种类加载器
- 启动类加载器(Bootstrap):加载核心类库
- 扩展类加载器(Extension):加载 ext 目录
- 应用类加载器(App):加载 ClassPath
3. 双亲委派模型
- 收到加载请求 → 向上委托父加载器
- 父加载器无法加载 → 自己加载
- 优点:安全、避免重复加载、保护核心类
4. 破坏双亲委派场景
- SPI(JDBC)
- Tomcat 类加载隔离
- OSGi
- 热部署
八、JVM 常用参数(生产必备)
1. 堆内存
plaintext
-Xms2g 初始堆
-Xmx2g 最大堆(建议 = Xms)
-Xmn1g 年轻代大小
2. 元空间
plaintext
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
3. GC 日志
plaintext
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:gc.log
4. 回收器指定
plaintext
-XX:+UseG1GC 使用 G1
-XX:+UseConcMarkSweepGC 使用 CMS
九、JVM 性能调优思路
1. 调优目标
- 降低 Full GC 频率与耗时
- 保证低延迟 或高吞吐量
- 避免 OOM
2. 调优步骤
- 监控:看 GC 频率、堆使用、线程状态
- 定位:是内存不足?对象过多?内存泄漏?
- 调整:堆大小、分代比例、回收器
- 验证:压测 + 观察 GC 日志
3. 典型问题
- 频繁 Full GC
- 老年代资源耗尽
- 大对象过多
- 内存泄漏
- Young GC 太频繁
- Eden 太小
- 瞬时流量大
- OOM: Java heap space
- 内存不足
- 死循环创建对象
- 大集合未释放
十、线上问题排查工具
1. JDK 自带工具
- jps:查看 Java 进程
- jstat:GC 统计、堆使用
- jmap:导出堆快照
- jhat:分析堆快照
- jstack:查看线程栈、死锁
2. 可视化 / 进阶工具
- MAT:内存泄漏分析
- Arthas:阿里开源,线上实时诊断
- VisualVM:JVM 监控分析
十一、高频面试核心总结
- JVM 内存结构:栈、堆、方法区、程序计数器、本地方法栈
- 堆分代:年轻代(Eden/S0/S1)+ 老年代
- 对象生命周期:Eden → Survivor 轮换 → 老年代
- GC 算法:复制、标记清除、标记整理
- 双亲委派:向上委托,保证安全
- 调优核心:减少 Full GC,合理设置堆大小