JVM 核心概念深度解析
最近正在复习Java八股,所以会将一些热门的八股问题,结合ai与自身理解写成博客便于记忆
一、JVM内存结构/运行时数据区
JVM运行时数据区主要分为以下几个部分:
-
程序计数器(PC Register)
- 线程私有,记录当前线程执行的字节码行号
- 唯一不会出现OOM的内存区域
-
Java虚拟机栈(VM Stack)
- 线程私有,存储栈帧(局部变量表、操作数栈、动态链接、方法出口等)
- 可能出现StackOverflowError和OutOfMemoryError
-
本地方法栈(Native Method Stack)
- 线程私有,为Native方法服务
- 同样可能出现StackOverflowError和OutOfMemoryError
-
Java堆(Heap)
- 线程共享,存放对象实例和数组
- GC主要工作区域,分新生代(Eden、Survivor)和老年代
- 可能出现OutOfMemoryError
-
方法区(Method Area)
- 线程共享,存储类信息、常量、静态变量等
- JDK 8后由元空间(Metaspace)实现,使用本地内存
- 可能出现OutOfMemoryError
-
运行时常量池
- 方法区的一部分,存放编译期生成的字面量和符号引用
二、对象分配与逃逸分析
创建对象不一定分配在堆里,JVM会通过以下技术优化对象分配:
-
逃逸分析(Escape Analysis)
- 分析对象作用域是否逃逸出方法或线程
- 未逃逸对象可进行栈上分配或标量替换
-
栈上分配(Stack Allocation)
- 将未逃逸对象分配在栈上,随栈帧出栈自动销毁
- 减少GC压力
-
标量替换(Scalar Replacement)
- 将未逃逸对象拆解为基本类型变量
- 完全避免对象分配
-
TLAB(Thread Local Allocation Buffer)
- 每个线程在Eden区私有的分配区域
- 避免多线程竞争指针碰撞
- 默认大小为Eden区的1%
三、垃圾回收机制
根可达性算法
- 从GC Roots出发,标记所有可达对象
- GC Roots包括:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
- 同步锁持有的对象
- JMXBean等系统对象
三色标记法
- 白色:未被访问的对象
- 灰色:已被访问但引用未扫描完的对象
- 黑色:已被访问且引用已扫描完的对象
- 作用:并发标记时追踪对象状态
- 好处:支持并发标记,减少STW时间
漏标问题解决方案
-
增量更新(Incremental Update)
- CMS采用,记录新增引用关系
- 写屏障(Write Barrier)实现
-
原始快照(Snapshot At The Beginning, SATB)
- G1采用,记录删除的引用关系
- 写屏障实现
浮动垃圾问题
- 产生原因:并发清理阶段用户线程产生的新垃圾
- 解决方案:
- 下次GC时清理
- CMS通过预留空间(-XX:CMSInitiatingOccupancyFraction)避免并发失败
四、垃圾收集器详解
CMS(Concurrent Mark Sweep)
设计目标与特点
- 目标:最小化老年代收集的停顿时间
- 类型:老年代收集器(与ParNew配合使用)
- 算法:标记-清除(Mark-Sweep)算法
- 工作模式:并发收集(与用户线程并行)
详细工作流程
-
初始标记(Initial Mark) - STW
- 暂停所有应用线程(STW)
- 标记GC Roots直接关联的对象
- 时间极短(通常1-10ms)
-
并发标记(Concurrent Mark)
- 与用户线程并发执行
- 从GC Roots开始遍历整个对象图
- 时间较长但不需要STW
-
重新标记(Remark) - STW
- 再次暂停所有应用线程
- 处理并发标记期间的对象变化
- 使用增量更新算法解决漏标问题
- 比初始标记时间长但远短于并发标记
-
并发清除(Concurrent Sweep)
- 与用户线程并发执行
- 清理未被标记的垃圾对象
- 使用空闲列表(free-list)管理内存
G1(Garbage First)
- 特点:
- 分Region的内存布局
- 可预测的停顿模型(-XX:MaxGCPauseMillis)
- 混合回收(Young GC + Mixed GC)
详细工作流程
-
Young GC
- STW操作
- 回收所有Eden和Survivor区
- 存活对象复制到新的Survivor或晋升到Old区
-
并发标记周期
- 初始标记(Initial Mark):STW,与Young GC同步进行
- 根区域扫描(Root Region Scan):扫描Survivor区引用
- 并发标记(Concurrent Mark):全堆对象标记
- 最终标记(Final Mark):STW,处理SATB缓冲区
- 清理(Cleanup):统计各Region存活对象
-
Mixed GC
- 回收部分Young和Old Region
- 基于停顿预测模型选择收益最高的Region
- 通过-XX:MaxGCPauseMillis控制目标停顿时间
-
四个阶段:
- 初始标记(STW)
- 并发标记
- 最终标记(STW)
- 筛选回收(STW)
-
优点:高吞吐兼顾低延迟,适合大堆
-
缺点:内存占用较高
五、类加载机制
双亲委派模型
-
加载流程:
- 子加载器先委托父加载器加载
- 父加载器无法完成时子加载器尝试加载
-
好处:
- 避免类重复加载
- 保证核心类安全(如java.lang.Object)
- 实现类的层次化隔离
破坏双亲委派
-
方式:
- SPI机制(如JDBC):使用线程上下文类加载器
- OSGi:网状类加载模型
- 热部署:自定义类加载器
-
Tomcat的实现:
- Web应用类优先自行加载
- 共享类库由Common类加载器加载
- 不同Web应用隔离(JSP类使用独立加载器)
六、JVM调优经验
-
内存调优:
- -Xms/-Xmx:堆初始/最大大小(建议设为相同值)
- -Xmn:新生代大小(建议占堆1/3到1/2)
- -XX:MetaspaceSize/-XX:MaxMetaspaceSize:元空间大小
-
GC调优:
- -XX:+UseG1GC:启用G1收集器
- -XX:MaxGCPauseMillis=200:目标停顿时间
- -XX:InitiatingHeapOccupancyPercent=45:G1触发并发周期阈值
-
监控工具:
- jstat:GC统计
- jmap:堆内存分析
- jstack:线程分析
- VisualVM/JProfiler:图形化分析
-
常见问题处理:
- OOM:分析堆/栈/元空间dump
- CPU高:定位热点线程/方法
- 长时间GC:调整收集器或内存比例