目录
[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 个阶段:
-
加载 :类加载器读取
.class文件,生成Class对象; -
链接:
-
验证:校验类文件是否符合 JVM 规范;
-
准备:为静态变量分配内存并赋默认初值 (如
int为 0); -
解析:将常量池中的符号引用替换为直接内存地址;
-
-
初始化 :执行静态代码块,为静态变量赋程序定义的初始值;
-
使用:创建对象、调用方法等;
-
卸载:类从方法区被垃圾回收(即方法区回收的核心动作)。
二、方法区回收的核心条件(面试必考)
方法区回收的目标是不再使用的类 ,一个类能被卸载,必须同时满足以下 3 个条件:
-
实例无引用:此类及其所有子类的实例对象,在堆中已无任何引用(即所有实例都被回收);
-
类对象无引用 :该类对应的
java.lang.Class对象(类元数据对象、类对象),在任何地方都未被引用; -
类加载器无引用:加载该类的类加载器,在任何地方都未被引用。
只有 3 个条件全部满足,JVM 才会在 Full GC 时卸载该类,释放方法区内存。
三、方法区回收的特点
-
回收频率极低:
-
日常业务中,类加载器(如系统类加载器)、
Class对象、实例通常长期被引用,很难同时满足 3 个卸载条件; -
因此方法区回收效率远低于堆,仅在 Full GC 时才会触发。
-
-
可触发场景:
-
自定义类加载器加载临时类,使用完成后主动释放类加载器、
Class对象、实例的引用; -
热部署、插件化场景下,卸载模块时主动清理所有引用,触发类卸载。
-
垃圾判定算法
一、核心问题:如何判断对象可回收?
Java 中判断对象是否可回收,本质是看对象是否还被有效引用;若没有任何引用,则视为 "死亡对象",可被 GC 回收。
特殊情况:循环引用的对象也能被回收(前提是没有使用引用计数法)。
二、两种主流判定算法
2.1 引用计数法(Reference-Counting)
-
基本思路 :给每个对象维护一个引用计数器:
-
当对象被新增引用指向时(即有其他对象 / 变量引用它)→ 计数器 +1;
-
当指向该对象的引用失效时(如引用变量赋值为 null)→ 计数器 -1;
-
计数器 = 0 → 说明无任何对象 / 变量引用它,该对象可回收。
-
-
优点:
实现简单、判定高效(Objective-C、Python 等语言使用)。
-
缺点:
-
引用 / 去引用都要做加减运算,影响性能;
-
无法解决循环引用问题(如 A 引用 B,B 引用 A,两者计数器永远不为 0,无法回收);
-
-
现状 :主流 Java 虚拟机(如 HotSpot)已完全弃用。
2.2 可达性分析算法(Java 采用)
-
核心优势:解决了引用计数法的循环引用问题,是 Java/C# 等语言的标准算法。
-
基本思路:
-
以 GC Root 集合为起点,从上到下遍历所有引用对象;
-
能被 GC Root 引用链到达的对象 → 存活对象(标记为存活);
-
无法被到达的对象 → 死亡对象(标记为垃圾,可回收)。
-
-
直观理解:不在 GC Root 引用关系网中的对象,就是垃圾。
可作为 GC Root 的对象:
-
静态变量:属于类,生命周期贯穿程序运行,是天然的根引用;
-
活动线程:正在运行的线程,其引用的对象必须保留;
-
栈帧中局部变量 / 方法参数:方法执行期间的临时引用,方法结束后失效;
-
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)
-
核心流程:
-
标记:通过可达性分析,标记所有存活对象;
-
清除:对堆内存从头到尾进行线性便遍历,如果发现某个对象没有被标记为可达对象,则将其回收。
-

-
优点:基础算法,无需额外空间。
-
缺点:
-
效率低:需要两次遍历堆内存(标记 + 清除);
-
内存碎片:清除后产生大量不连续内存碎片,分配大对象时难以找到连续空间。
-
2 复制算法(Copying)
-
核心流程:
-
将内存分为 From 区 和 To 区(大小相等),新对象只分配到 From 区;
-
GC 时,将 From 区所有存活对象复制到 To 区,并按顺序排列;
-
清空 From 区,交换 From/To 角色,下次继续分配。
-
-
优点:
-
效率高:仅复制存活对象,适合存活率低的场景;
-
无内存碎片:存活对象在 To 区连续排列。
-
-
缺点:
-
内存浪费:默认只能使用一半内存;
-
不适合高存活率场景:存活对象多则复制成本极高。
-
-
应用 :新生代(Minor GC),HotSpot 优化为 Eden + 2 个 Survivor(8:1:1),缓解内存浪费问题。
3 标记 - 压缩算法(Mark-Compact)
-
核心流程:
-
标记:同标记 - 清除,标记所有存活对象;
-
压缩:将所有存活对象向一端移动,按顺序排列;
-
清除:直接清理边界外的死亡对象。
-
-
优点:
-
无内存碎片;
-
无内存浪费,内存利用率高。
-
-
缺点:
- 效率低:存活对象多则移动成本高,比标记 - 清除更耗时。
-
应用 :老年代,常与标记 - 清除混合使用(多次标记清除后再压缩,减少移动次数)。
三、分代收集算法(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