搜索站内内容

← 返回文章列表

JVM 学习 02:垃圾回收、类加载与问题排查

按 JVM 八股原始问题整理 GC Roots、四种引用、回收算法、收集器、类加载和线上排障。

JasonBackendJavaJVMGCG1Observability约 2,900 字大约 8 分钟
云雾在层叠的山岭之间流动

这一篇对应原始 JVM 笔记中的“垃圾回收”“类加载”和“问题排查”部分。重点不是只记住某一个收集器名称,而是把对象是否可回收、如何回收、类何时进入 JVM、线上出问题如何拿证据这几条线连起来。

JVM 如何判断对象是否可以回收

判断对象是否存活,常见思路有引用计数和可达性分析。引用计数给对象维护计数器:被引用时加一,引用失效时减一,计数归零就可以回收。它实现直观,但无法解决循环引用。两个对象即使已经与业务完全断开,只要互相引用,计数就不会归零。

HotSpot 实际使用可达性分析。从一组 GC Roots 出发沿引用链向下搜索;不能从任何根到达的对象,才会被判定为不可达。GC Roots 常见来源包括:

  • 虚拟机栈中局部变量引用的对象;
  • 本地方法栈中 JNI 或 native 方法引用的对象;
  • 类静态字段、运行时常量池引用的对象;
  • 活跃线程本身,以及被同步锁持有的对象。

这也是排查内存泄漏时要看引用链的原因。两个对象相互引用不是根因;需要继续向上找,是谁让这整组对象仍然从 Roots 可达。反过来,不可达只表示对象具备回收资格,不代表它会立刻被回收,实际回收时机仍由分配压力和收集器策略决定。

Java 有哪四种引用

强、软、弱、虚引用的强度依次减弱。强引用最常见,例如 Object value = new Object();只要强引用仍在,GC 不会回收该对象。软引用在内存不足时才可能被回收,过去常被用于内存敏感缓存;但实际缓存仍应有容量、过期、命中率和主动淘汰策略,不能把软引用当作缓存治理本身。

弱引用的生命周期更短。GC 扫描到只被弱引用关联的对象时,会在本次回收中清理它;WeakHashMap、ThreadLocalMap 的 key 等场景能够看到这种设计。需要注意的是 ThreadLocal 的 value 并不会因为 key 是弱引用就自动安全,在线程池里仍要 remove()

虚引用最弱,get() 永远拿不到实际对象,必须与 ReferenceQueue 一起使用。它主要用来跟踪对象即将被回收的事件;NIO 或 Cleaner 相关机制可以据此协助释放堆外资源。它不是“更高级的缓存引用”,而是一种资源生命周期通知手段。

常见的垃圾回收算法有哪些

标记—清除算法

标记阶段找出存活对象,清除阶段回收未标记对象。优点是实现直接,缺点有两个:标记和清除都需要遍历,效率受对象数量影响;清除后会留下不连续的空闲区域,长期运行后可能出现内存碎片。CMS 的老年代回收就与这一类思路有关,因此碎片也是它需要面对的问题。

标记—复制算法

把可用空间分为两部分,回收时将存活对象复制到另一部分,再整体清空旧区域。它不会留下碎片,且当大多数对象很快死亡时复制成本很低,因此非常适合年轻代。代价是必须预留复制空间,若存活对象比例高,复制量会明显增加。

标记—整理算法

先标记存活对象,再把它们向内存一端移动,最后清理边界外的区域。它保留连续空闲空间,不需要像复制算法那样始终保留一半区域;代价是移动对象会带来额外工作和停顿。老年代收集器常使用整理或与之相近的策略。

分代收集

分代并不是一种独立的标记方法,而是根据对象存活周期将不同算法组合起来。年轻代对象通常存活率低,适合复制;老年代对象存活时间长,收集器会使用更适合长期对象的策略。理解分代之后,就不会把“新生代回收”和“Full GC”简单看作同一种事件。

常见垃圾收集器有哪些

Serial 与 Serial Old 使用单线程回收,适合资源有限或简单场景,回收时会暂停工作线程。ParNew 可看作 Serial 的多线程版本;Parallel Scavenge 与 Parallel Old 更关注吞吐,常用于后台计算、批处理等对交互停顿不那么敏感的场景。

CMS 的目标是缩短停顿。它包含初始标记、并发标记、重新标记和并发清除:初始标记与重新标记需要 Stop-The-World,并发标记和清除可以与应用线程交错执行。它的代价包括对 CPU 资源敏感、无法很好处理并发阶段产生的浮动垃圾、以及标记清除带来的碎片。CMS 已在较新的 JDK 中被移除,因此理解其设计比在新项目中选择它更有意义。

G1 的特点和执行过程

G1 把堆划分成许多 Region,而不是把整块堆固定切为一整片年轻代和老年代。它仍保留分代思想,但可以根据回收收益选择 Region,在给定的停顿时间预算内优先回收更“划算”的区域,这也是 Garbage-First 的含义。

从整体上看,G1 有整理效果;从局部 Region 迁移看,类似复制算法。它的主要特点可以概括为并行与并发、分代收集、空间整合和可预测停顿。可预测并不等于绝不超时:对象存活率、分配速度、堆大小、CPU 资源和应用行为都决定最终暂停是否能满足目标。

G1 的一个典型周期包括:初始标记,短暂停顿并标记与 Roots 直接关联的对象;并发标记,与业务线程并行遍历可达对象;最终标记,短暂停顿以处理并发阶段遗留的引用变化;筛选回收,根据 Region 的回收价值迁移存活对象并回收旧 Region。读 GC 日志时,应把这些阶段、暂停时长、回收前后占用和应用分配率放在一起判断。

类加载过程是什么

类从字节码到可用,通常经历加载、连接和初始化。连接又包括验证、准备和解析。加载阶段将 .class 字节流转换为 JVM 内部的类结构并创建 Class 对象;验证检查字节码和类型安全;准备阶段为静态字段分配空间并赋默认值;解析阶段把符号引用替换为直接引用;初始化阶段才执行静态字段显式赋值和静态代码块。

数组类没有独立的 class 文件,通常由 JVM 直接创建;但它的元素类型仍遵循相应的加载规则。理解“准备阶段默认值”和“初始化阶段真正赋值”的区别,能解释不少静态初始化顺序问题。

类加载器有哪些

Bootstrap ClassLoader 负责加载 Java 核心类库;平台类加载器负责平台范围类库;应用类加载器通常加载 classpath 或模块路径下的业务代码;应用也可以定义自定义类加载器,从网络、加密包或插件目录加载类。不同 JDK 版本对历史名称和具体路径有演进,阅读时应区分概念与某一版本的实现细节。

类加载器的职责不只是“找到 class 文件”,也参与类身份。一个类通常由“类的全限定名 + 定义它的类加载器”共同确定。插件隔离、热部署和多版本依赖之所以复杂,正是因为同名类由不同加载器加载时,可能是不同的类型。

双亲委派模型是什么

典型加载流程是:子加载器收到请求后先让父加载器尝试,父级无法加载时再由子级处理。这样可以防止业务代码用同名 java.lang.String 覆盖核心类,同时尽量保证核心类型的唯一性。它不是每个框架都绝对不可改变的规则;SPI、容器和插件框架会在特定方向上调整加载委派,但需要清楚隔离和安全代价。

排查 Metaspace 持续增长时,除了看类数量,也要看旧类加载器是否被静态缓存、线程上下文类加载器或第三方单例持有。只要加载器仍可达,它加载的类通常也难以卸载。

CPU 飙高怎么排查

先用 top 或等价工具找到高 CPU Java 进程,再通过 top -H -p <pid> 找到热点线程。线程 ID 通常以十进制展示,而 jstack 中的 nid 常用十六进制,需要转换后匹配。定位到线程栈后再判断:若持续在业务循环或序列化、正则等计算路径,检查算法和输入;若主要是 GC 线程,结合 GC 日志与堆占用;若是锁竞争或自旋,继续看持锁线程和等待链。

单次线程 dump 只是一瞬间。对持续问题,应在相邻时间点采集多份 dump,确认栈是否稳定;对偶发问题,则要把采集时刻与请求量、延迟和 GC 指标关联。

OOM 怎么排查

服务应预先开启堆转储,例如 -XX:+HeapDumpOnOutOfMemoryError,并指定可写的转储目录。发生 OOM 后先确认异常类型:Java heap space、Metaspace、direct memory 与 native 内存不足的排查入口不同。堆 OOM 可以使用 MAT 等工具查看 Dominator Tree、对象数量与从 GC Roots 开始的引用链;同时检查 GC 日志、容器内存限制、直接内存和线程数,避免只看堆大小。

常见根因包括缓存没有容量边界、消息或任务在无界队列中堆积、ThreadLocal 未清理、动态代理或热部署导致类加载器泄漏、短时间创建大量对象导致分配率超过回收能力。加大内存参数只是在容量确实不足时有效;若对象不该存活,它只会让故障更晚出现。

死锁怎么排查

死锁发生时,多个线程分别持有资源并等待对方释放。jstack <pid> 的输出通常能够识别 Java 层面的死锁并列出参与线程、持有锁和等待锁。修复时优先统一资源获取顺序;需要可取消或可超时的路径,可以使用 tryLock 等机制给等待增加退出条件;还应避免持锁调用慢 I/O 或未知外部代码。

面试总结

GC 题的核心顺序是:先用 GC Roots 判断可达性,再区分引用强度和回收算法,接着理解收集器如何权衡吞吐、停顿与碎片;类加载题的核心是加载、连接、初始化和加载器隔离;线上排障则要从线程栈、GC 日志、heap dump 和时间线建立证据。把这几部分连起来,才能从八股回答走到真实 JVM 问题的定位。

FIELD NOTES / DISCUSS

文章讨论

读完后,欢迎留下你的补充、疑问或不同看法。

250 浏览

全部评论 (0)

正在读取评论…

    GUEST IDENTITY

    设置访客身份

    评论、回复与留言板将复用这份身份。

    选择头像