news 2026/4/18 11:51:09

GC 垃圾回收器忙半天,在清理什么?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GC 垃圾回收器忙半天,在清理什么?

JDK每次大版本更新,会有新的GC垃圾回收器ZGC、Shenandoah等,然后我们就的没完没了的学,死记硬背这些过几天很容易忘了。但如果弄明白GC垃圾回收器它们的本质在干什么,就比较容易记忆了。

认真搞清楚一个最基础、却最容易被忽略的问题:JVM里什么样的对象,才配叫垃圾?你可能会说:这还不简单?不用的对象就是垃圾呗。

别急,如果你真这么想,那很可能你项目里的内存泄漏,就是这么来的。咱们下边从表象到本质,看看垃圾的定义、识别逻辑,以及为什么 JVM 的设计如此精妙。

不用 ≠ 垃圾

先破个误区,很多同学觉得:这个对象我后面肯定不会用了,JVM 应该把它回收掉。但现实是JVM根本不知道你用不用。

它就是个死心眼的程序,也不会分析你的业务逻辑,它只认一个铁律:只要程序还有可能访问到这个对象,它就不是垃圾。

哪怕你写完代码就忘了它,只要还有一条引用链能触达它,JVM 就会把它当活人供着,一分内存都不能动。

垃圾的判定标准,不是主观无用,是客观不可达

如何判断对象的可达

JVM判断可达性,靠的是一个叫做GC Roots的概念。

我举个例子方便理解他,想象一下:你手里拿着一串葡萄。

这串葡萄有主干,主干上分出小枝,小枝上挂着一颗颗葡萄粒。但有些葡萄粒已经掉了,有的是连着一小段旁枝一起掉在桌上。

现在,你用手捏住葡萄串的主干(这就是 GC Roots),把整串提起来。

所有还挂在串上、能被提起来的葡萄粒,都是活着的对象;而那些已经掉在桌面上、和主干彻底断开的,无论它们看起来多完整,都成了垃圾。

JVM 的垃圾回收,干的就是这件事:它不关心葡萄好不好吃,只关心你还能不能把它拎起来。

什么能做 GC Roots

能做 GC Roots 通常是我们平常接触过的一些变量和引用。

  • 当前正在执行的方法中的局部变量(比如Object obj = new Object();里的obj

  • 类的静态字段(static 变量)

  • 字符串常量池里的对象(比如"hello"

  • JNI(本地方法)中持有的 Java 对象引用

  • 被 synchronized 锁住的对象(某些 JVM 实现)

注意:new 对象本身不是 Root,指向它的 obj 引用才是

举个例子:

public void demo() { Object a = new Object(); // ← 这个 new Object() 能通过局部变量 a 访问 → 存活 } // 方法结束,a 出栈 → 引用消失 → 对象不可达 → 成为垃圾

看明白了吗?对象的生死,取决于有没有路能走到它。

JVM怎么找路的?

那么问题来了:JVM怎么知道哪些对象有路?答案是:遍历引用图,做标记

整个过程分两步:

  1. 从所有 GC Roots 出发,深度/广度遍历所有引用链

  2. 给所有能访问到的对象打上“存活”标记

做完这两步,剩下的对象没被打标的统统视为垃圾。

你可以想象成:JVM在内存里玩扫雷,标出所有安全区,剩下的全是雷(垃圾),等着清理。

这个过程通常需要Stop-The-World(STW),也就是暂停你的应用线程。

为什么?

因为如果一边跑业务一边改引用,遍历结果就不准了,可能刚标完活,下一秒就被置 null 了。

特殊引用类型

我们知道 Java 不只有强引用。它还提供了其他三种引用,让开发者能更精细地控制对象生命周期。

引用类型

是否阻止回收

典型用途

强引用

(默认)

普通对象,只要存在就不会被回收

软引用

(SoftReference)

内存不足时回收

缓存系统(如图片缓存)

弱引用

(WeakReference)

下次 GC 就回收,适合监听器、映射表

虚引用

(PhantomReference)

无法获取对象,仅用于跟踪回收事件

重点来了:只有强引用才算真正的路径;其他引用在 GC 眼里≈断头路。

比如:这也是为什么WeakHashMap能自动清理 key,它的 key 是弱引用,一旦外部不再强引用,key 就清除了。

WeakReference<Object> ref = new WeakReference<>(new Object()); // 如果没有其他强引用,这个对象在下一次 GC 时就会被回收

垃圾 = 不可达对象

到这我们可以给出精确的定义了:JVM中垃圾是指:从任意 GC Root 出发,都无法通过引用链访问到的对象。

注意,这里有几个关键词:

  • 任意 GC Root:只要有一个 Root 能到达,就不是垃圾;

  • 引用链:必须是强引用构成的路径;

  • 当前时刻:可达性是动态的,对象可能“由活变死”。

为什么理解这个很重要?

因为你写的每一行代码,都在影响可达性,很多写法正在阻止垃圾回收。这些是实际开发中不经意间影响可达性的常见写法。

1.把对象塞进static集合却不清理

users是 GC Root(静态变量),里面所有对象永远可达 →内存泄漏,老年代缓慢增长直至 OOM。

public class Cache { private static List<User> users = new ArrayList<>(); public void addUser(User u) { users.add(u); // 加进去就不管了? } }
2.监听器 / 回调未注销

事件总线通常持有强引用,即使页面/组件已关闭,对象仍被持有 →Activity / Controller / Service 无法回收(Android / Spring 常见坑)。

eventBus.register(this); // 注册监听器 // ... 但对象销毁时忘了 unregister
3.内部类隐式持有外部类引用

Runnable匿名内部类隐式持有Outer实例引用。如果该Runnable被长期持有(如提交到线程池),整个Outer对象(含大数组)都无法回收。可以改用static class或 lambda(不捕获外部实例)。

public class Outer { private byte[] data = new byte[1024 * 1024]; // 大对象 public Runnable getTask() { return new Runnable() { // 非静态内部类 public void run() { /* ... */ } }; } }
4. ThreadLocal 使用后未 remove()

ThreadLocal的值由线程的Thread对象间接持有(Thread -> ThreadLocalMap -> Value)。在线程池中,线程复用 →Value 永远不释放→ 内存泄漏。可以在try-finally中调用remove()

private static ThreadLocal<BigObject> local = new ThreadLocal<>(); public void process() { local.set(new BigObject()); // 忘记 local.remove(); }
5. 大对象频繁创建又很快丢弃

大对象直接进入老年代(JVM 默认 > 一半 Eden 区的对象算大对象),快速撑爆老年代 →触发 Full GC 甚至 OOM

for (int i = 0; i < 100000; i++) { byte[] buffer = new byte[1024 * 1024]; // 1MB 大数组 // 用完就丢 }
6. 字符串拼接产生大量临时对象(尤其在循环中)

产生大量短命StringBuilderString对象,加剧新生代 GC 压力。

String s = ""; for (int i = 0; i < 10000; i++) { s += "item" + i; // 每次都 new StringBuilder + toString() }

GC 它只是忠实地执行可达即活,不可达即死的规则。而我们要做的,就是确保真的不用的对象,确实不可达。

写在最后

Java 的 GC 机制看似复杂,有 Serial、Parallel、CMS、G1、ZGC……

但万变不离其宗:所有 GC 垃圾回收器,干的都是同一件事,找出活的对象,剩下的就是垃圾,在想办法腾出内存。

换句话说:GC 不是在找垃圾,而是在救活人。救完之后,场地怎么拆、怎么平,才是不同回收器的手艺差别。

与其死记 G1 的 Region 或 ZGC 的着色指针,不如先搞懂:什么对象会被救?什么对象会被放弃?为什么?这才是调优、排障、避免内存泄漏的真正起点。

看完等于学会,点个赞吧!!!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 12:32:16

2026年经济触底回升,程序员春天要来了,备战春招Java面试题分享!

2026年经济触底回升&#xff0c;程序员春天要来了&#xff0c;备战春招Java面试题分享&#xff01;经济复苏与程序员就业前景根据国际货币基金组织(IMF)最新预测&#xff0c;2026年全球经济将结束长达4年的下行周期&#xff0c;进入新一轮增长阶段。随着5G、人工智能、物联网等…

作者头像 李华
网站建设 2026/4/18 11:04:23

React Native轮播组件实战:react-native-snap-carousel深度应用

在移动应用开发中&#xff0c;轮播组件是提升用户体验的重要元素。react-native-snap-carousel作为React Native生态中的高性能轮播解决方案&#xff0c;为开发者提供了丰富的布局选项和流畅的动画效果&#xff0c;让创建精美的轮播界面变得异常简单。 【免费下载链接】react-n…

作者头像 李华
网站建设 2026/4/18 9:40:13

UniHacker完全指南:快速掌握Unity许可证验证处理技术

Unity作为全球最流行的游戏开发引擎之一&#xff0c;其许可证验证机制常常给开发者带来困扰。UniHacker作为一款专业的跨平台Unity许可证验证处理工具&#xff0c;能够帮助开发者轻松解决这一问题。本文将为您提供从基础操作到高级技巧的完整指导。 【免费下载链接】UniHacker …

作者头像 李华
网站建设 2026/4/18 8:55:39

Linux设备模型

Linux设备模型笔记学习整理基于野火鲁班猫教程并且添加自己学习后理解的内容然后还有ai的一些总结。如果有说的不好或者不对的地方希望大家指正&#xff01;&#xff01;&#xff01;在开始之前先讲一下一级指针&#xff0c;二级指针和指针数组。举例&#xff0c;一级指针是 in…

作者头像 李华
网站建设 2026/4/18 8:54:57

人工智能训练师认证教程(2)Python os入门教程

目录 一、os模块简介 二、常用目录和文件操作 1. 路径操作 2. 目录操作 三、文件信息与权限 四、数据读取的实用技巧 1. 批量处理文件 2. 递归搜索文件 3. 安全文件操作 五、实际应用示例&#xff1a;数据文件分析器 六、实用技巧与最佳实践 1. 使用pathlib替代os.p…

作者头像 李华
网站建设 2026/4/18 12:34:08

分布式AI智能调度终极指南:让闲置设备变身高性能计算集群

分布式AI智能调度终极指南&#xff1a;让闲置设备变身高性能计算集群 【免费下载链接】exo Run your own AI cluster at home with everyday devices &#x1f4f1;&#x1f4bb; &#x1f5a5;️⌚ 项目地址: https://gitcode.com/GitHub_Trending/exo8/exo 你是否曾面…

作者头像 李华