← 返回首页
一、JVM 内存结构
线程私有
- 程序计数器:当前线程执行的字节码行号指示器,唯一不会 OOM 的区域
- 虚拟机栈:每个方法对应一个栈帧(局部变量表、操作数栈、动态链接、返回地址),默认大小 512K~1M,过深递归导致 StackOverflowError
- 本地方法栈:为 Native 方法服务,HotSpot 中与虚拟机栈合二为一
线程共享
- 堆:对象实例分配的主要区域,分为新生代(Eden + S0 + S1,默认 8:1:1)和老年代
- 方法区(元空间):JDK8 用 Metaspace(本地内存)替代永久代,存储类信息、常量、静态变量
- 运行时常量池:方法区的一部分,存放编译期生成的字面量和符号引用
- 直接内存:NIO 的 DirectByteBuffer 分配,不受 JVM 堆大小限制,受物理内存限制
二、类加载机制
类加载过程
- 加载:通过全限定名获取二进制字节流 → 转化为方法区运行时数据结构 → 生成 Class 对象
- 验证:文件格式验证、元数据验证、字节码验证、符号引用验证
- 准备:为类变量分配内存并设置零值(
static int a = 10 此阶段 a=0)
- 解析:符号引用替换为直接引用
- 初始化:执行
<clinit> 方法,真正赋值 static 变量、执行 static 块
双亲委派模型
- Bootstrap ClassLoader → Extension ClassLoader → Application ClassLoader → 自定义 ClassLoader
- 先委托父加载器加载,父加载器无法加载时才自己加载,保证核心类库安全
- 打破双亲委派:SPI(ServiceLoader)、OSGi、Tomcat(每个 WebApp 独立 ClassLoader)
三、垃圾回收
判断对象存活
- 引用计数法:无法解决循环引用,JVM 不采用
- 可达性分析:从 GC Roots(栈引用、静态变量、常量、JNI 引用)出发,不可达即为垃圾
- 四种引用:强引用 → 软引用(内存不足回收)→ 弱引用(下次 GC 回收)→ 虚引用(跟踪回收)
GC 算法
- 标记-清除:产生内存碎片
- 标记-复制:新生代使用,Eden + S0 → S1,空间换时间
- 标记-整理:老年代使用,标记后存活对象向一端移动
垃圾收集器
- Serial / Serial Old:单线程,Client 模式默认
- ParNew:Serial 的多线程版本,常与 CMS 搭配
- CMS:初始标记 → 并发标记 → 重新标记 → 并发清除,低停顿但有碎片
- G1:Region 分区,可预测停顿时间,JDK9 默认收集器
- ZGC:JDK15 正式可用,停顿时间 <1ms,基于染色指针 + 读屏障
四、JVM 调优
常用参数
-Xms / -Xmx:堆初始/最大大小,建议设为相同避免动态扩容
-Xmn:新生代大小;-XX:MetaspaceSize:元空间初始大小
-XX:+UseG1GC:启用 G1;-XX:MaxGCPauseMillis=200:目标停顿时间
-XX:+HeapDumpOnOutOfMemoryError:OOM 时自动 dump 堆快照
故障排查工具
jps:查看 Java 进程;jstat -gcutil:GC 统计
jmap -dump:format=b,file=heap.hprof:导出堆快照
jstack:线程快照,排查死锁和线程阻塞
- Arthas:阿里开源诊断工具,支持 dashboard / thread / trace / watch 等命令
五、JIT 编译与优化
- 解释执行 vs 编译执行:HotSpot 采用混合模式,热点代码触发 JIT 编译为本地机器码
- 热点探测:方法调用计数器(默认阈值 10000 次)+ 回边计数器(循环体)
- C1 / C2 编译器:C1 快速编译(Client),C2 深度优化(Server),分层编译结合两者
- 常见优化:方法内联、逃逸分析(栈上分配、标量替换)、锁消除、循环展开