Skip to content

JVM 知识总结

目录


1. 内存模型

1.1 运行时数据区(JDK 8)

区域线程是否共享作用
程序计数器私有当前线程执行的字节码行号指示器;Native 方法时为空
Java 虚拟机栈私有存储栈帧:局部变量表、操作数栈、动态链接、方法出口;栈深度超限 → StackOverflowError
本地方法栈私有为 Native 方法服务
共享存放对象实例、数组;无法扩展 → OutOfMemoryError
方法区共享类元信息、常量、静态变量、JIT 编译后的代码;JDK 8 用元空间 MetaSpace 实现,使用本地内存

要点

  • 堆、方法区为线程共享;程序计数器、虚拟机栈、本地方法栈为线程私有。
  • 直接内存(Direct Memory)不属于运行时数据区,但 OOM 时也可能抛出 OutOfMemoryError。

1.2 堆内存分区

  • 新生代(Young Generation)
    • Eden:新对象优先分配在这里。
    • Survivor:两个 Survivor(From / To),复制算法时用于保留存活对象,默认比例 Eden : From : To ≈ 8 : 1 : 1。
  • 老年代(Old Generation)
    • 存活多次 GC 的对象晋升到老年代;大对象(超过阈值)可能直接进入老年代。

对象创建简要过程

  1. 类加载检查 → 2. 分配内存(指针碰撞 / 空闲列表)→ 3. 初始化零值 → 4. 设置对象头 → 5. 执行 <init>

对象内存布局

  • 对象头:Mark Word(哈希、GC 年龄、锁信息等)、类型指针。
  • 实例数据:字段。
  • 对齐填充:8 字节对齐。

1.3 方法区与元空间(JDK 8)

  • JDK 7:方法区在堆中,用永久代(PermGen)实现。
  • JDK 8:永久代移除,用 元空间(Metaspace) 在本地内存实现,存类元信息等,不再占用堆,避免 PermGen OOM,仅受本地内存限制。

面试重点

  • 堆存对象实例;栈存局部变量和栈帧;方法区/元空间存类信息与常量。
  • 为什么要有两个 Survivor:复制算法需要一块“保留区”,两个 Survivor 轮换,避免内存碎片,适合新生代大量“朝生夕死”的对象。

2. 垃圾回收

2.1 如何判定对象可回收

  • 引用计数:有循环引用问题,Java 未采用。
  • 可达性分析:从 GC Roots 出发,不可达则视为可回收。
    • GC Roots:栈中引用、静态变量、常量、JNI 引用等。
  • 引用类型:强引用、软引用、弱引用、虚引用;只有强引用会阻止回收。

2.2 垃圾回收算法

算法思路优点缺点
标记-清除标记可回收,再统一清除实现简单产生碎片
标记-复制存活对象复制到另一块区域无碎片、高效空间折半
标记-整理标记后存活对象向一端移动无碎片、连续移动有开销
  • 新生代:多采用标记-复制(对象存活率低)。
  • 老年代:多采用标记-清除或标记-整理(对象存活率高,复制成本大)。

2.3 常见垃圾回收器

回收器区域特点
Serial新生代单线程,STW,适合单核/小堆
ParNew新生代多线程版 Serial,常与 CMS 搭配
Parallel Scavenge新生代多线程,关注吞吐量
Serial Old老年代单线程,标记-整理
Parallel Old老年代多线程,与 Parallel Scavenge 搭配
CMS老年代并发标记清除,低停顿,有碎片、并发阶段占用 CPU
G1全堆分区(Region),可预测停顿,兼顾吞吐与低延迟
ZGC / Shenandoah全堆超低延迟,适合大堆

面试重点

  • CMS:并发收集,减少 STW;缺点为碎片、并发阶段 CPU 敏感、可能产生浮动垃圾。
  • G1:将堆划分为多个 Region,优先回收价值高(回收收益大)的 Region,适合大堆和停顿时间敏感场景。

2.4 Minor GC、Major GC、Full GC

  • Minor GC:新生代 GC,较频繁,速度较快。
  • Major GC:通常指老年代 GC(有时与 Full GC 混用)。
  • Full GC:整堆 + 方法区/元空间回收,STW 最长,应尽量减少发生。

3. 类加载机制

3.1 类加载过程

  1. 加载(Loading)
    将 class 文件读入内存,生成 Class 对象。
  2. 链接(Linking)
    • 验证:格式、字节码、符号引用等。
    • 准备:为类静态变量分配内存并设零值;static final 常量在准备阶段即可赋字面量。
    • 解析:将常量池中的符号引用替换为直接引用。
  3. 初始化(Initialization)
    执行 <clinit>,为静态变量赋真实初值并执行静态块。

面试重点

  • 准备阶段只做默认零值,不执行赋值语句;初始化阶段才执行静态赋值和 static 块。
  • 触发初始化的典型场景:new、反射、调用静态方法/静态字段(非常量)、子类初始化时先触发父类初始化、主类等。

3.2 双亲委派模型

  • 定义:类加载请求先交给父加载器;父加载器无法加载时,子加载器才尝试加载。
  • 层次:Bootstrap → Extension → Application(系统类加载器)→ 自定义类加载器。
  • 作用:避免类被重复加载;保护核心类库不被应用层同名类替换(如自定义 java.lang.String 不会被加载)。

破坏双亲委派

  • 如 JDK 1.2 引入双亲委派前已有类加载器未遵循;SPI(如 JDBC)中 Bootstrap 需加载厂商实现,通过线程上下文类加载器(Context ClassLoader)加载。
  • 热部署、OSGi 等场景会按模块使用不同类加载器,也会打破“单一委派”的约束。

3.3 自定义类加载器

  • 继承 ClassLoader,重写 findClass()(建议),在内部调用 defineClass() 将字节数组转为 Class
  • 一般不重写 loadClass(),否则会破坏双亲委派;重写 findClass() 可在保持委派的前提下自定义“从哪里读 class”。

面试重点

  • 同一类由同一加载器加载时,才认为“相等”(equals);不同加载器加载的同一 class 文件会得到不同类。

4. 性能调优与监控

4.1 常用 JVM 参数(示例)

text
# 堆
-Xms512m                    # 初始堆
-Xmx512m                    # 最大堆(建议与 -Xms 相同,避免动态扩展)
-Xmn256m                    # 新生代(老年代 = 堆 - 新生代)
-XX:SurvivorRatio=8         # Eden : Survivor = 8 : 1 : 1

# 元空间(JDK 8)
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m

# GC 选择
-XX:+UseG1GC                # 使用 G1
-XX:MaxGCPauseMillis=200    # 期望最大停顿(G1 等)

# GC 日志(JDK 8)
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log

# GC 日志(JDK 9+)
-Xlog:gc*:file=gc.log:time,level,tags

4.2 内存泄漏排查思路

  • 现象:堆持续增长、Full GC 频繁但回收不多、最终 OOM。
  • 步骤
    1. 使用 jmap -heap <pid> 看堆概况;jmap -dump:format=b,file=heap.hprof <pid> 导出堆快照。
    2. 用 MAT、JProfiler 等分析:查看支配树、大对象、重复/异常集合(如未关闭的集合不断加入对象)。
    3. 结合业务:未关闭的连接、监听器未移除、静态集合无限增长、ThreadLocal 未 remove 等。

4.3 GC 日志简要解读

  • 关注:Young GC / Full GC 频率、每次停顿时间、回收前后各区域容量变化。
  • 若 Young GC 频繁且回收少:可能是新生代过小或短生命周期对象过多。
  • 若 Full GC 频繁且老年代居高不下:可能存在老年代堆积或泄漏。

4.4 常用监控与诊断工具

工具/命令作用
jps列出 Java 进程
jstat查看 GC、类加载、编译等统计(如 jstat -gc <pid> 1000
jmap堆信息、堆转储(-heap-dump
jstack线程快照,查死锁、阻塞(jstack <pid>
jinfo查看/修改 JVM 参数
VisualVM图形化监控堆、线程、CPU、GC
Arthas在线诊断:反编译、查看堆栈、监控方法调用、追踪等

面试重点

  • 线上 CPU 飙高:先用 top 定位进程和线程,再用 jstack 看该线程栈,结合代码定位。
  • OOM:堆转储 + MAT 分析,看哪些对象占用量大、是否合理、是否有泄漏路径。

小结

  • 内存:堆(对象)、栈(栈帧)、方法区/元空间(类元信息);堆分新生代与老年代,新生代常用复制算法。
  • GC:可达性分析判活;算法有标记-清除、标记-复制、标记-整理;回收器按场景选 Serial、CMS、G1、ZGC 等。
  • 类加载:加载 → 链接(验证、准备、解析)→ 初始化;双亲委派保证核心类安全;自定义类加载器一般重写 findClass()
  • 调优与排查:合理设置堆与 GC 参数;结合 jstat、jmap、jstack、堆转储与 MAT/Arthas 做内存与 CPU 问题定位。

Released under the MIT License.