JVM面试题——垃圾回收GC

目录

[C/C++ vs Java 内存管理](#C/C++ vs Java 内存管理)

方法区垃圾回收

垃圾判定算法

垃圾回收算法

[Java 四种引用类型](#Java 四种引用类型)

默认垃圾收集器版本对应


C/C++ vs Java 内存管理

1. C/C++:手动回收

  • 无自动垃圾回收机制,对象不再使用时必须手动调用 delete 释放内存

  • 若忘记释放,会导致内存泄漏 (无用对象占着内存不释放),长期积累最终引发内存溢出(OOM)

  • 优点:灵活可控;缺点:易出错,对程序员要求高。

2. Java:自动回收(GC)

  • 引入 ** 自动垃圾回收(GC)** 机制,由 JVM 垃圾回收器自动回收不再使用的对象;

  • GC 主要负责堆内存回收,也会回收方法区(效率极低);

  • 线程私有区域(程序计数器、虚拟机栈、本地方法栈)随线程生命周期销毁,无需 GC

  • 优点:简化开发,避免手动内存管理错误;缺点:GC 时机不可控,STW 可能影响性能。

方法区垃圾回收

一、类的生命周期

类从加载到卸载共 5 个阶段:

  1. 加载 :类加载器读取 .class 文件,生成 Class 对象;

  2. 链接

    • 验证:校验类文件是否符合 JVM 规范;

    • 准备:为静态变量分配内存并赋默认初值 (如 int 为 0);

    • 解析:将常量池中的符号引用替换为直接内存地址;

  3. 初始化 :执行静态代码块,为静态变量赋程序定义的初始值

  4. 使用:创建对象、调用方法等;

  5. 卸载:类从方法区被垃圾回收(即方法区回收的核心动作)。

二、方法区回收的核心条件(面试必考)

方法区回收的目标是不再使用的类 ,一个类能被卸载,必须同时满足以下 3 个条件

  1. 实例无引用:此类及其所有子类的实例对象,在堆中已无任何引用(即所有实例都被回收);

  2. 类对象无引用 :该类对应的 java.lang.Class 对象(类元数据对象、类对象),在任何地方都未被引用;

  3. 类加载器无引用:加载该类的类加载器,在任何地方都未被引用。

只有 3 个条件全部满足,JVM 才会在 Full GC 时卸载该类,释放方法区内存。

三、方法区回收的特点

  • 回收频率极低:

    • 日常业务中,类加载器(如系统类加载器)、Class 对象、实例通常长期被引用,很难同时满足 3 个卸载条件;

    • 因此方法区回收效率远低于堆,仅在 Full GC 时才会触发。

  • 可触发场景:

    • 自定义类加载器加载临时类,使用完成后主动释放类加载器、Class 对象、实例的引用;

    • 热部署、插件化场景下,卸载模块时主动清理所有引用,触发类卸载。

垃圾判定算法

一、核心问题:如何判断对象可回收?

Java 中判断对象是否可回收,本质是看对象是否还被有效引用;若没有任何引用,则视为 "死亡对象",可被 GC 回收。

特殊情况:循环引用的对象也能被回收(前提是没有使用引用计数法)。

二、两种主流判定算法

2.1 引用计数法(Reference-Counting)

  • 基本思路 :给每个对象维护一个引用计数器

    • 当对象被新增引用指向时(即有其他对象 / 变量引用它)→ 计数器 +1;

    • 当指向该对象的引用失效时(如引用变量赋值为 null)→ 计数器 -1;

    • 计数器 = 0 → 说明无任何对象 / 变量引用它,该对象可回收。

  • 优点

    实现简单、判定高效(Objective-C、Python 等语言使用)。

  • 缺点

    1. 引用 / 去引用都要做加减运算,影响性能

    2. 无法解决循环引用问题(如 A 引用 B,B 引用 A,两者计数器永远不为 0,无法回收);

  • 现状主流 Java 虚拟机(如 HotSpot)已完全弃用

2.2 可达性分析算法(Java 采用)

  • 核心优势:解决了引用计数法的循环引用问题,是 Java/C# 等语言的标准算法。

  • 基本思路

    1. GC Root 集合为起点,从上到下遍历所有引用对象;

    2. 能被 GC Root 引用链到达的对象 → 存活对象(标记为存活);

    3. 无法被到达的对象 → 死亡对象(标记为垃圾,可回收)。

  • 直观理解:不在 GC Root 引用关系网中的对象,就是垃圾。

可作为 GC Root 的对象:

  1. 静态变量:属于类,生命周期贯穿程序运行,是天然的根引用;

  2. 活动线程:正在运行的线程,其引用的对象必须保留;

  3. 栈帧中局部变量 / 方法参数:方法执行期间的临时引用,方法结束后失效;

  4. JNI 引用 :Java 本地接口(JNI)调用的C/C++ 本地代码中引用的 Java 对象,生命周期由本地代码管理(注:本地代码≠Java 代码,是 Java 调用的 C/C++ 代码)。

垃圾回收算法

一、前置概念:STW 与吞吐量

1. Stop-the-World(STW)

  • 定义:JVM 执行 GC 时,暂停所有用户线程,仅保留 GC 线程运行,直到 GC 完成。

  • 特点:任何 GC 算法都会触发 STW,GC 优化的核心目标就是减少 STW 时长,提升系统响应速度。

2. 吞吐量

  • 公式:吞吐量 = 执行用户代码时间 / (执行用户代码时间 + GC 时间)

  • 意义:吞吐量越高,说明 CPU 花在业务代码上的时间越多,GC 效率越高。

二、三大基础 GC 算法

1 标记 - 清除算法(Mark-Sweep)

  • 核心流程

    1. 标记:通过可达性分析,标记所有存活对象;

    2. 清除:对堆内存从头到尾进行线性便遍历,如果发现某个对象没有被标记为可达对象,则将其回收。

  • 优点:基础算法,无需额外空间。

  • 缺点

    • 效率低:需要两次遍历堆内存(标记 + 清除);

    • 内存碎片:清除后产生大量不连续内存碎片,分配大对象时难以找到连续空间。

2 复制算法(Copying)

  • 核心流程

    1. 将内存分为 From 区To 区(大小相等),新对象只分配到 From 区;

    2. GC 时,将 From 区所有存活对象复制到 To 区,并按顺序排列;

    3. 清空 From 区,交换 From/To 角色,下次继续分配。

  • 优点

    • 效率高:仅复制存活对象,适合存活率低的场景;

    • 无内存碎片:存活对象在 To 区连续排列。

  • 缺点

    • 内存浪费:默认只能使用一半内存;

    • 不适合高存活率场景:存活对象多则复制成本极高。

  • 应用新生代(Minor GC),HotSpot 优化为 Eden + 2 个 Survivor(8:1:1),缓解内存浪费问题。

3 标记 - 压缩算法(Mark-Compact)

  • 核心流程

    1. 标记:同标记 - 清除,标记所有存活对象;

    2. 压缩:将所有存活对象向一端移动,按顺序排列;

    3. 清除:直接清理边界外的死亡对象。

  • 优点

    • 无内存碎片;

    • 无内存浪费,内存利用率高。

  • 缺点

    • 效率低:存活对象多则移动成本高,比标记 - 清除更耗时。
  • 应用老年代,常与标记 - 清除混合使用(多次标记清除后再压缩,减少移动次数)。

三、分代收集算法(Generational Collection)

不是一个新算法,是多种算法的结合。

1. 核心思想

没有最优算法,只有最适合的算法:根据对象生命周期,将堆分为新生代和老年代,分别采用不同算法。

2. 新生代(Young Gen)

  • 特点:区域小、对象存活率极低(朝生夕死,90% 以上)。

  • 算法选择:复制算法

    • 理由:存活对象少,复制效率高;

    • HotSpot 实现:Eden : S0 : S1 = 8 : 1 : 1,仅浪费 10% 内存,解决了复制算法的内存浪费问题。

3. 老年代(Tenured Gen)

  • 特点:区域大、对象存活率极高(长期存活)。

  • 算法选择:标记 - 清除 或 标记 - 清除 + 标记 - 压缩混合

    • 理由:存活对象多,复制算法成本太高;混合实现可平衡效率与内存碎片问题。

四、算法对比总结

对比维度 复制算法 标记 - 清除算法 标记 - 压缩算法
内存效率 最高(仅复制存活) 中等 最低(移动 + 整理)
内存整齐度 最高(无碎片) 最低(碎片多) 中等
内存利用率 最低(浪费空间)
适用场景 新生代(低存活) 老年代(混合用) 老年代(混合用)

Java 四种引用类型

一、核心概述

Java 提供了 4 种引用类型,用于控制对象的回收时机

二、四种引用详解

1 强引用(Strong Reference)

  • 回收策略永不自动回收,只要强引用存在,GC 永远不会回收该对象。

  • 代码示例

复制代码
User user = new User(1, "zhangsan"); // 强引用
User user1 = user; // 新增强引用
user = null; // 断开一个强引用
System.gc(); // 强制 GC,对象仍不会被回收(还有 user1 引用)
  • 特点

    • 默认的引用类型(new 创建的对象都是强引用);

    • 只有当所有强引用都断开(如赋值为 null),对象才会成为可回收对象。

  • 适用场景:日常业务对象,保证对象在使用期间不会被意外回收。

2 软引用(Soft Reference)

  • 回收策略内存不足时才回收,在 OOM 抛出前,会将软引用对象列入回收范围进行二次回收。

  • 代码示例

复制代码
SoftReference<User> userSoftRef = new SoftReference<>(new User(1, "zhangsan"));
User user = userSoftRef.get(); // 获取对象
// 内存紧张时,GC 会回收该对象,再次 get() 会返回 null
  • 特点

    • 内存充足时,和强引用一样保留对象;

    • 内存不足时,优先回收软引用对象,避免 OOM。

  • 适用场景内存敏感的缓存(如本地缓存 EHCache、Netty 缓存),缓存对象在内存不足时可被清理,释放空间。

3 弱引用(Weak Reference)

  • 回收策略发现即回收,只要发生 GC,无论内存是否充足,都会回收只被弱引用关联的对象。

  • 代码示例

复制代码
WeakReference<User> userWeakRef = new WeakReference<>(new User(1, "zhangsan"));
System.out.println(userWeakRef.get()); // 输出对象
System.gc(); // 触发 GC
System.out.println(userWeakRef.get()); // 输出 null(对象已被回收)
  • 特点

    • 生命周期极短,仅存活到下一次 GC 之前;

    • 比软引用更 "脆弱",GC 一旦执行就会被回收。

  • 适用场景临时对象 / 缓存(如 ThreadLocal 底层实现、WeakHashMap),避免长期占用内存。

4 虚引用(Phantom Reference)

  • 别名:幽灵引用、幻影引用

  • 回收策略对象回收跟踪,无法通过虚引用获取对象实例,唯一作用是在对象被回收时收到系统通知。

  • 代码示例

复制代码
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
obj = null; // 解除强引用
System.gc(); // 建议 GC
// 对象被回收后,phantomRef 会进入 queue,可通过 isEnqueued() 判断
boolean isCollected = phantomRef.isEnqueued();
  • 特点

    • 完全无法获取对象实例(get() 永远返回 null);

    • 必须配合 ReferenceQueue 使用,用于监听回收事件。

  • 适用场景对象回收监控 / 资源清理(如堆外内存释放、资源泄漏检测)。

三、四种引用对比表

引用类型 回收时机 核心特点 典型场景
强引用 永不回收(除非无引用) 默认引用类型,最安全 日常业务对象
软引用 内存不足 OOM 前回收 内存敏感,避免 OOM 本地缓存、内存敏感缓存
弱引用 每次 GC 必回收 生命周期极短,发现即回收 ThreadLocal、WeakHashMap
虚引用 对象被回收时通知 无法获取对象,仅用于监控回收 堆外内存释放、回收监控

默认垃圾收集器版本对应

JDK 版本 默认垃圾收集器组合
JDK 7 Parallel Scavenge + Serial Old
JDK 8 及 JDK 7u40+ Parallel Scavenge + Parallel Old
JDK 9+ G1
Oracle JDK 17 G1
OpenJDK 17 Shenandoah

查看命令

复制代码
java -XX:+PrintCommandLineFlags -version

JDK 8:-XX:+UseParallelGC

JDK 17:-XX:+UseG1GC

相关推荐
白宇横流学长2 小时前
化妆刷生产管理系统分析与设计
java
cch89182 小时前
PHP vs 易语言:Web开发与桌面编程大对决
开发语言·前端·php
洛_尘2 小时前
MiniMQ(单元测试报告)
java·测试
江公望2 小时前
GNU C语句表达式,10分钟讲清楚
c语言·开发语言·c++
初中就开始混世的大魔王2 小时前
3.2 DDS 层-Domain
开发语言·c++·中间件
一轮弯弯的明月2 小时前
有序整数对个数-欧拉函数
java·算法·蓝桥杯·学习心得
lifewange2 小时前
Java 自动化测试参数化实现
java·数据库·sqlserver
码上农民2 小时前
Idea2025.3.3专业版安装和无限试用
java·ide·intellij-idea
CDN3603 小时前
CDN 回源异常、源站压力大?负载均衡与回源策略优化
java·运维·负载均衡