垃圾回收: JVM帮程序员做了什么?

欢迎浏览高耳机的博客

希望我们彼此都有更好的收获

感谢三连支持!  

        🙉上篇博客介绍了JVM的类加载机制,它负责将代码从编译器生成的.class文件动态加载到运行时,确保程序的顺利执行。JVM类加载三步解读: 双亲委派模型如何维护Java生态-优快云博客https://blog.youkuaiyun.com/Chunfeng6yugan/article/details/144350327?spm=1001.2014.3001.5501
        类加载机制只是JVM的一部分,而内存管理才是编程中更为关键的环节。JVM内置的垃圾回收器和算法能有效管理内存,减少泄漏风险。因此,本篇博客将重点聚焦于JVM的垃圾回收机制。

目录

一、为什么要进行垃圾回收?

 二、垃圾回收主要回收哪个内存区域?

三、标记的过程

(1) 引用计数

优点

缺点

(2) 可达性分析

直白解释:

举个例子:

四、回收的过程

(1) 标记 - 清除算法

(2) 复制算法

(3) 标记 - 整理算法

(4) 分代算法

五、垃圾回收器的典型实现


一、为什么要进行垃圾回收?

        🍉程序运行过程中会不断创建和使用对象,而Java中存放着几乎所有的对象实例,这些对象占用内存。如果这些对象在使用完毕后,内存没有被释放,就会导致内存泄漏。随着时间推移,内存逐渐被耗尽,程序将无法分配新的内存,最终可能导致系统崩溃。垃圾回收机制就是为了解决这一问题,它能够自动释放不再使用的内存,使程序无需手动管理内存,减少内存泄漏的风险,提高程序的可靠性和开发效率。

 二、垃圾回收主要回收哪个内存区域?

        🍊对于程序计数器虚拟机栈和本地方法栈,其生命周期与线程紧密绑定,随线程的启动而创建,随线程的结束而销毁。这些区域的内存分配和回收具有明确的规律,当方法执行完毕或线程终止时,相关内存会自动释放。

        元数据区中需要加载的类对象,通常都是有上限的。因此,垃圾回收主要针对的是堆内存中的对象。堆内存是运行时数据区中最大的一部分,用于存储通过 new 关键字创建的对象。

三、标记的过程

        🍍垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象哪些还存活,哪些已经“死去”。判断对象是否已“死”(可回收)有如下两种常用的标记算法:

(1) 引用计数

        该方法为每个对象维护一个引用计数器,每当一个地方引用该对象时,计数器加 1;当引用失效时,计数器减1。当计数器降为 0 时,对象不再被引用,可被回收。

优点

        实现简单:能够快速判断对象是否存活。

缺点
  1. 消耗额外空间
    例如,一个类中只有一个int成员(4个字节),为了使用引用计数,至少需要一个short(2个字节)来存储引用计数器。这意味着内存额外占用了50%。

  2. 无法解决循环引用问题
    例如,两个对象相互引用,但又都不可达。此时,它们的引用计数都不会降为0,导致无法被垃圾回收器回收。

🥥Java(JVM)并没有采用引用计数标记方法

(2) 可达性分析

        可达性分析的大致逻辑是,垃圾回收器从 GC Roots(垃圾回收根节点) 出发,沿着引用链尽可能多地访问对象,标记它们为存活对象。这个过程类似于在图中进行遍历,而不是树的遍历。

直白解释:

  1. GC Roots 是起点:GC Roots 是一组特殊的对象,比如虚拟机栈中的局部变量、方法区中的静态变量、本地方法栈中的JNI引用等。

  2. 从起点出发:垃圾回收器从这些 GC Roots 开始,沿着对象之间的引用关系(比如对象A引用对象B)进行访问。

  3. 尽可能访问更多对象:垃圾回收器会尽可能多地通过引用链访问到其他对象,标记这些对象为“存活”。

  4. 图的遍历:因为对象之间的引用关系可能形成复杂的图结构(比如循环引用),所以这个过程更像是图的遍历,而不是树的遍历。

举个例子:

        🍑假设有一个对象A,它引用了对象B,而对象B又引用了对象C。垃圾回收器从GC Roots(比如对象A)出发,通过引用链访问到对象B,再从对象B访问到对象C。如果对象D没有被任何对象引用,那么它就无法被访问到,最终会被标记为垃圾回收的目标。

 

四、回收的过程

        🍅垃圾回收阶段会根据不同的垃圾回收算法,对已标记的可回收对象进行处理。以下是几种常见的垃圾回收算法及其回收过程:

(1) 标记 - 清除算法

标记阶段

        通过可达性分析法直接针对内存中的对应对象进行释放。

清除阶段

        清除未被标记的对象,并回收其占用的内存。该算法简单,但容易产生内存碎片,并且标记和清除的效率都较低。

(2) 复制算法

标记阶段

        同样通过可达性分析法标记存活对象。

回收阶段

        将存活对象复制到另一个内存区域,然后清理原内存区域。这样每次回收时都是一次对整个半区的回收,内存分配时也不需要考虑内存碎片等问题,只需要移动堆顶指针,按顺序分配即可。不过,复制算法需要有较大的内存空间作为担保,空间利用率较低。同时,如果被标记的存活对象较大较多,那么复制的开销成本也会增大。

(3) 标记 - 整理算法

标记阶段

        同样通过可达性分析法标记存活对象。

回收阶段

        让所有存活的对象都向一端移动,然后清理掉端边界以外的内存。这样可以避免标记 - 清除算法产生的内存碎片问题,但其标记和整理的效率相对较低。

(4) 分代算法*

分代

        根据对象的存活周期将内存划分为不同的区域,每一轮GC过后,存活对象的"年龄"则会+1,通常包括新生代和老年代。新生代中存在"伊甸区","幸存区0"和"幸存区1"。新生代中的对象大多是“朝生夕灭”的,存活率低,而老年代中的对象存活率高。

垃圾回收

        新的对象将会被创建在"伊甸区",一轮GC过后,被标记的存活对象会被移动至随机一个"幸存区"。多轮GC过后,"年龄"较大的对象会被移动至老年代。新生代使用复制算法,老年代使用标记 - 整理或标记 - 清除算法(根据对象存活率选择合适的算法)。

🥑分代回收,是JVM的GC中的基本思想方法

 

五、垃圾回收器的典型实现

        🫐自 Java 语言诞生以来,垃圾收集器也随之出现,而如今众多的垃圾收集器是随着技术发展而逐渐演变的产物。

        最早的垃圾收集器是 Serial,它是一个单线程的垃圾收集器,仅支持串行执行。随后,为了提升性能,出现了 Serial Old,这是专为老年代设计的单线程收集器。随着时间推移,为了进一步提高效率,人们开发了 ParNew,这是 Serial 的多线程版本,能够并行执行垃圾回收任务。

        然而,随着对性能要求的不断提高,人们开始追求更高的吞吐量,即单位时间内成功回收垃圾的数量。因此,出现了 Parallel ScavengeParallel Old,这两个垃圾收集器分别针对新生代和老年代,以吞吐量优先为目标,通过多线程并行回收来提升效率。

        随后,为了兼顾吞吐量和减少垃圾回收的停顿时间,CMS(Concurrent Mark Sweep) 垃圾收集器应运而生。CMS 是一个并发标记-清除收集器,能够在垃圾回收过程中与用户线程并发执行,从而显著减少停顿时间。在 JDK 1.8(含)之前,CMS 是大多数业务系统的主流垃圾收集器。

        然而,CMS 也存在一些局限性,例如在高并发场景下可能出现“并发模式失败”(Concurrent Mode Failure),导致 Full GC 的频繁触发。为了解决这些问题,JDK 1.8 引入了 G1(Garbage First) 垃圾收集器。G1 是一个基于区域划分的垃圾收集器,它将堆内存划分为多个独立的区域(Region),并根据垃圾回收的优先级动态选择回收区域。G1 的目标是在几乎不需要停止程序的情况下完成垃圾回收,从而实现低延迟和高吞吐量的平衡。


 

!!!        了解Minor GC 和 Full GC?这两种 GC 有什么不一样?

Minor GC(新生代 GC)
        Minor GC 是指发生在新生代的垃圾收集。由于 Java 对象大多具有“朝生夕灭”的特性,即大部分对象在创建后很快就会失效,因此 Minor GC 非常频繁。它通常采用复制算法,回收速度较快。新生代的 Minor GC 主要负责清理 Eden 区和 Survivor 区,将存活对象复制到另一个 Survivor 区,然后清理 Eden 区和当前使用的 Survivor 区。

Full GC(老年代 GC 或 Major GC)
        Full GC 是指发生在老年代的垃圾收集。当 Full GC 发生时,通常会伴随至少一次的 Minor GC(但并非绝对,例如在 Parallel Scavenge 收集器中,可能会直接触发 Full GC)。老年代的对象生命周期较长,因此 Full GC 的频率相对较低,但每次执行的时间通常比 Minor GC 慢 10 倍以上。老年代的垃圾回收通常采用标记-整理算法或标记-清除算法,以处理对象的长期存活和内存碎片问题。


        🙉本篇博客探讨了 JVM 的垃圾回收机制,解释了其重要性、主要针对的内存区域(堆内存)、标记和回收的过程等问题。通过这些内容,你可以快速掌握垃圾回收的核心知识点,了解如何通过选择合适的回收器和优化策略提升程序性能,从而更好地利用 JVM 的自动内存管理功能来保障 Java 的稳定运行。 


希望这篇博客能为你理解JVM垃圾回收提供一些帮助

如有不足之处请多多指出

我是高耳机

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值