JVM 详解
内存结构
程序计数器(PC Register)
- 当前线程执行的字节码行号指示器;各线程独立;唯一不会 OOM 的区域。
虚拟机栈(Java 栈)
- 每线程一个栈;每方法一个栈帧。栈帧含:局部变量表、操作数栈、动态链接、方法出口等。
- 局部变量表以 Slot 为单位;long/double 占 2 Slot。栈深度超限抛 StackOverflowError;可动态扩展时无法申请到内存抛 OutOfMemoryError。
- -Xss 设置栈容量。
本地方法栈
- 为 Native 方法服务;HotSpot 中与虚拟机栈合一。
堆(Heap)
- 存放对象实例与数组;GC 主要区域。逻辑上分新生代、老年代;新生代分 Eden、Survivor0、Survivor1(From/To)。
- 新生代:大多数对象在这里分配并很快被回收。老年代:长期存活对象、大对象(直接进老年代的策略视收集器而定)。
- -Xms 初始堆、-Xmx 最大堆,建议设成相同避免动态扩容。-Xmn 新生代大小。-XX:SurvivorRatio=8 表示 Eden:From:To=8:1:1。
方法区(元空间)
- 存类信息、常量、静态变量、JIT 编译后代码。JDK8 用元空间(Metaspace)在本地内存实现,不再用永久代。
- -XX:MetaspaceSize、-XX:MaxMetaspaceSize。OOM:类元数据过多或泄漏。
直接内存
- NIO 使用的堆外内存,受 -XX:MaxDirectMemorySize 限制;分配时可能触发 Full GC。
类加载机制
过程
- 加载:通过全限定名取二进制流;转为方法区数据结构;生成 Class 对象。
- 链接:验证(格式、元数据、字节码、符号引用);准备(静态变量赋零值,final 常量赋实际值);解析(符号引用→直接引用,可延迟到使用前)。
- 初始化:执行 <clinit>(静态块与静态变量赋值);父类先初始化;接口实现类初始化时不要求父接口先初始化。
双亲委派
- 类加载器:Bootstrap(加载 lib 下核心类)、Extension(ext 目录)、Application(类路径)。
- 收到加载请求先委派给父加载器;父无法加载时自己加载。保证核心类由 Bootstrap 加载,避免被替换。
- 破坏双亲委派:JDK1.2 前 findClass、SPI(如 JDBC 用线程上下文类加载器加载驱动)、OSGi 等模块化。
自定义类加载器
- 继承 ClassLoader,重写 findClass;不破坏双亲委派则重写 findClass,否则重写 loadClass。
垃圾回收
判定死亡
- 引用计数:有循环引用问题,Java 不用。
- 可达性分析:从 GC Roots 出发,不可达即可回收。GC Roots:栈中引用、静态变量、常量、JNI 引用等。
引用类型
- 强、软(SoftReference,内存不足时回收)、弱(WeakReference,下次 GC 必回收)、虚(PhantomReference,用于跟踪回收)。
回收算法
- 标记-清除:标记后统一清除;有碎片。
- 标记-复制:分两块,存活对象复制到另一块;适合新生代,Eden+Survivor 即此思想。
- 标记-整理:标记后存活对象向一端移动;适合老年代,无碎片但移动有成本。
收集器
- Serial:单线程,STW;新生代复制。
- ParNew:多线程版 Serial,配合 CMS 用。
- Parallel Scavenge:多线程新生代,关注吞吐量;-XX:MaxGCPauseMillis、-XX:GCTimeRatio。
- Serial Old:老年代单线程标记-整理。
- Parallel Old:老年代多线程标记-整理,与 Parallel Scavenge 搭配。
- CMS:并发标记清除,目标低停顿。步骤:初始标记(STW)→ 并发标记 → 重新标记(STW)→ 并发清除。缺点:碎片、浮动垃圾、并发阶段占用 CPU。-XX:+UseConcMarkSweepGC。
- G1:分区(Region),可设定停顿目标。Young GC + 并发标记 + Mixed GC(回收部分 Old)。-XX:+UseG1GC、-XX:MaxGCPauseMillis。
- ZGC:低延迟,着色指针、读屏障;支持 TB 级堆。JDK15 生产可用。
调优工具
jstack
- 打印线程栈;查死锁、线程卡在何处。
jstack <pid>;可配合 -l 打印锁信息。
jmap
- 堆信息、堆 dump。
jmap -heap <pid>、jmap -dump:format=b,file=heap.hprof <pid>。dump 会 STW,线上慎用或选低峰。
jstat
- 查看 GC 统计:
jstat -gc <pid> 1000 每秒打印;-gcutil 看占比。
jvisualvm
- 图形化:监控堆、线程、CPU、采样、dump 分析。
arthas
- 阿里开源;attach 到进程,不重启排查。thread 看线程、jad 反编译、watch 观察方法入参返回值、heapdump dump、trace 追踪调用链。
OOM 排查
- 堆溢出:dump 用 MAT/VisualVM 看占用最大的对象、是否泄漏(如集合只增不减)。
- 元空间溢出:类加载过多、重复加载;检查 CGLIB、反射、动态类。
- 栈溢出:-Xss 调大或查递归/调用链是否过深。
- 直接内存:-XX:MaxDirectMemorySize、NIO 使用不当。
CPU 100% 排查
- top 找进程;top -Hp <pid> 找线程;把线程 id 转成 16 进制,jstack <pid> 中查对应 nid,看该线程栈在做什么(如死循环、频繁 GC)。
- 若是 GC 导致:jstat 看 GC 频率与耗时;考虑堆大小、对象分配速率、老年代是否过小。
Full GC 频繁
- 老年代满或元空间满触发。看 jstat 中 Old 区、Metaspace 变化;dump 看大对象、是否有内存泄漏;调整堆与比例、检查代码里大对象与静态集合。