网站首页 > 文章精选 正文
Java的垃圾回收(Garbage Collection,GC)就像是一个自动的"清洁工",它会自动帮你清理掉程序中不再使用的内存,防止内存泄漏。下面我用生活中的例子来比喻,让你轻松理解这个机制。
一、垃圾回收的基本概念
想象你的房间就是内存:
创建对象:就像你买新东西放进房间。
垃圾对象:就像你不再需要的东西(比如过期的杂志、坏掉的玩具)。
垃圾回收:就像定期打扫房间,把没用的东西扔掉。
Java的清洁工(GC)会自动帮你完成这个打扫工作,你不需要自己手动清理(不像C/C++需要手动释放内存)。
二、垃圾是如何被识别的?
清洁工如何知道哪些是垃圾?主要有两种方式:
1. 引用计数法(基本不用)
给每个对象贴个标签,记录有多少"绳子"(引用)连着它。
当标签显示0时,说明没人用这个对象了。
问题:如果两个垃圾对象互相牵着(循环引用),标签永远不是0,就清理不掉。
2. 可达性分析(Java实际使用)
从一些固定的"挂钩"(GC Roots)出发,看哪些对象能被"绳子"直接或间接够到,够不到的对象就是垃圾。
GC Roots包括:
正在执行的方法中的局部变量
静态变量
活跃的线程等
三、垃圾回收算法(清洁工的打扫方式)
1. 标记-清除
工作方式:先标记所有垃圾;然后一次性清除。
优点:简单直接。
缺点:会产生内存碎片,就像房间清理后留下很多小空隙。
2. 复制算法
工作方式:把房间分成两半;只使用其中一半,当快满时;把有用的东西搬到另一半;然后清空原来的一半。
优点:没有碎片问题。
缺点:浪费一半空间。
3. 标记-整理
工作方式:标记所有垃圾;把有用的东西都推到一边(整理);清理边界外的空间。
优点:没有碎片,也不浪费空间。
缺点:整理过程较慢。
4. 分代收集
实际Java使用的方式:根据对象年龄采用不同策略。
内存分区:
新生代:新对象在这里。使用复制算法(因为大部分新对象很快变成垃圾),分为Eden区和两个Survivor区。
老年代:存活较久的对象,使用标记-清除或标记-整理。
永久代/元空间(方法区):存放类信息等。
四、垃圾回收过程(详细例子)
以最常见的分代收集为例:
1. 新对象诞生:
所有新对象都出生在"婴儿房"(Eden区)
当Eden区满了,触发Minor GC
2. 第一次筛选:
清理Eden区,存活对象搬到Survivor1区
给每个存活对象年龄+1(表示熬过了一次GC)
3. Survivor区轮换:
下次Eden区满时,清理Eden和Survivor1
存活对象搬到Survivor2区
两个Survivor区就这样来回倒腾
4. 晋升老年代:
当对象年龄达到阈值(默认15),搬到"成人房"(老年代)
大对象也会直接进老年代
5. 老年代GC:
当老年代也满了,触发Major GC(通常伴随Full GC)
这个过程比较慢,会暂停所有应用线程(Stop-The-World)
五、常见的垃圾收集器(不同类型的清洁工)
收集器 | 特点 | 适用场景 |
Serial | 单线程,简单高效 | 客户端小应用 |
ParNew | Serial的多线程版 | 配合CMS使用 |
Parallel Scavenge | 注重吞吐量 | 后台计算型应用 |
CMS | 并发标记清除,减少停顿 | 重视响应时间的Web应用 |
G1 | 分区收集,可控停顿 | JDK9+默认,通用性强 |
ZGC | 超低延迟(<10ms) | 超大堆内存应用 |
Shenandoah | 低延迟,并发整理 | 类似ZGC |
六、内存泄漏的常见情况
即使有GC,也可能出现"该扔的东西没扔掉":
静态集合:像static List一直添加元素但不移除
未关闭的资源:数据库连接、文件流等
监听器未注销:注册了事件监听但不用时不移除
不合理的作用域:将大对象定义在方法外但实际只用于方法内
七、如何优化GC?
减少垃圾产生:重用对象(使用对象池);避免在循环中创建临时对象。
合理设置JVM参数:
-Xms和-Xmx # 设置堆的初始和最大大小(设为相同值避免动态调整)
-XX:NewRatio # 新生代与老年代的比例
-XX:SurvivorRatio # Eden与Survivor区的比例
选择合适的收集器:
吞吐量优先:Parallel Scavenge + Parallel Old
低延迟优先:CMS或G1
八、实际工作中的应用
开发中不需要过度关注GC,但要注意:
高频Full GC会影响系统性能(表现为周期性卡顿)
监控工具(如VisualVM、GC日志)可以帮助分析问题
对于实时性要求高的系统,可以选择ZGC或Shenandoah
记住:Java的GC就像个尽责的清洁工,虽然它偶尔会暂停工作(Stop-The-World)来彻底打扫,但这样能保证你的程序长期稳定运行。理解它的工作原理,能帮助你写出更高效、更健壮的Java代码!
猜你喜欢
- 2025-07-23 JVM中哪些是线程共享区,哪些是线程独占区
- 2025-07-23 线上服务 FGC 问题排查,看这篇就够了
- 2025-07-23 JVM运行时内存区域、堆内存分段机制
- 2025-07-23 JVM运行数据区深度解析(jvm运行时区域)
- 2025-07-23 开发者必备!线上内存泄漏排查的 8 个 Linux 实战工具
- 2025-07-23 内存溢出满分排查步骤,看完不用担心拿不到Offer了
- 2025-07-23 关于JVM调优,看这篇就够了(jvm调优实战简书)
- 2025-07-23 那么大个对象的垃圾回收有什么不同
- 2025-07-23 GC分代年龄为什么是15?(对象gc的分代年龄保存在)
- 2025-07-23 JVM参数调优,一文让你搞懂,再也不为应用操心了!
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)
- mysql数据库面试题 (57)
- fmt.println (52)