​​JVM三:JVM垃圾回收机制(GC)

 系列文章: 

JVM一:JVM内存区域划分 

JVM二:JVM类加载机制

JVM三:JVM垃圾回收机制(GC)

目录

1.什么是垃圾?

2.垃圾回收

3.GC主要针对堆进行释放

4.实际工作过程

1.判定垃圾方法

1.1死亡对象判定方法

1.1.1 引用计数

1.1.2 可达性分析

2.对象释放方法

2.1 标记-清除算法

2.2 复制算法

2.3 标记-整理算法

2.4 分代回收算法


1.什么是垃圾?

指的是不再使用的内存。

2.垃圾回收

将不用的内存,自动释放,解决内存泄露问题。

3.GC主要针对堆进行释放

GC是以"对象"为基本单位,进行回收,而不是字节。

垃圾回收(GC)主要处理三种情况:

  1. 完全不使用的对象:当一个对象不再被任何其他对象或程序部分引用时,GC会将其识别为垃圾并回收。这意味着,只有在对象完全没有被引用时,才会成为垃圾回收的目标

  2. 部分引用的对象:如果一个对象仅部分属性或方法不再被使用,但整体上仍有引用存在,GC不会回收该对象。只有当对象作为一个整体不再需要时,才会考虑回收。

  3. 回收策略在执行垃圾回收时,GC会回收整个对象,而不会只回收对象的某一部分。即,垃圾回收器是基于对象整体来判定是否需要回收,而不是基于对象的部分属性或方法。

这种回收机制确保了内存的有效管理和程序的稳定性,避免因部分回收导致的程序错误或内存泄漏。


4.实际工作过程

1.判定垃圾方法

如何判断哪个对象是垃圾,哪个对象不是垃圾,关键思路:抓住这个对象,查看它到底有没有"引用"指向它?那应该如何具体知道对象是否有引用指向?

1.1死亡对象判定方法

1.1.1 引用计数

引用计数机制是一种垃圾回收策略,它通过给每个对象分配一个引用计数器来跟踪对象的引用情况。具体流程如下:

  1. 初始化引用计数器:当创建一个新的对象时,为其分配一个初始值为0的引用计数器。

  2. 增加引用计数:每当有一个引用指向该对象时,将该对象的引用计数器加1。这可以通过赋值操作、参数传递等方式实现。

  3. 减少引用计数:每当某个引用被销毁或不再指向该对象时,将该对象的引用计数器减1。

  4. 检查引用计数:当对象的引用计数器变为0时,意味着没有任何引用指向该对象,因此可以认为它是垃圾。此时,垃圾回收器会释放该对象所占用的内存空间。

引用计数机制的局限性可以从以下几个方面进行详细分析:

  1. 内存空间利用率低

    • 每个对象都需要一个额外的引用计数器来记录其被引用的次数。这个计数器会随着对象的创建而分配,无论对象的大小如何。
    • 假设每个引用计数器占用4个字节的存储空间,对于一个体积为1KB的对象,附加的空间消耗相当于增加了约4%的额外空间。而对于更小的对象,比如4字节的对象,引用计数器的体积与对象本身的体积相等,这意味着整体空间占用翻了一倍。
    • 这种空间的额外消耗在处理大量小型对象时尤为明显,可能导致显著的内存效率降低。
  2. 循环引用问题

    • 当两个或多个对象彼此之间相互引用,但没有其他活动路径可以访问这些对象时,就形成了所谓的循环引用。此时,即使这些对象不再被程序的其他部分使用,它们的引用计数也不会归零。
    • 例如,考虑两个对象(1号和2号)互相引用,但没有任何外部引用指向它们。如果销毁它们之间的相互引用,每个对象的引用计数将从1减到0。然而,由于它们彼此之间的存在引用,引用计数器不会归零。
    • 这种情况下,即使这两个对象实际上已经无法被程序访问,它们仍然不会被标记为垃圾,从而导致内存泄露。

1.1.2 可达性分析

       在Java中,对象之间通过引用相互联系,形成了复杂的链式或树形结构。

       以二叉树为例,一个对象的引用可能指向另一个对象,而这个对象的成员又可能指向其他对象,这样的关系构成了一个整体的网络。

      为了有效地管理内存,Java使用了可达性分析来标记和清除不再使用的对象

可达性分析的基本概念

  • 将所有Java对象视为通过引用连接成的树结构。
  • 从根节点(称为GC roots)开始遍历,标记所有可达的对象。
  • GC roots包括栈上的局部变量、常量池中的对象以及静态成员变量。
  • 不可达的对象,即从GC roots无法遍历到的对象,被视为垃圾,将被回收。

可达性分析的缺点

  1. 速度较慢:与引用计数相比,可达性分析需要遍历整个对象树,这通常是一个较慢的过程。
  2. 间歇性执行:尽管速度较慢,但可达性分析不需要持续执行。它通常会在特定的时间间隔内进行,以减少对系统性能的影响。

2.对象释放方法

2.1 标记-清除算法

       标记-清除(Mark-and-Sweep)算法分为“标记(Mark)”和清除(Sweep)”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。

如下图,灰色部分,为标记的对象。

但是这种算法,会产生明显的两个问题:

1.效率问题:标记和清除两个过程效率都不高;

2.内存碎片问题:标记清除后,被释放的空闲空间,是零散的,不是连续的,会有内存碎片问题。


2.2 复制算法

      复制(Copying)算法为了解决内存碎片问题而出现的,它将内存分成大小相同的两块,然后将"不是垃圾"的对象复制到另一半,然后再把使用的空间一次性清理掉。

 虽然复制算法解决了内存碎片问题,但是它也存在两个明显的问题:

1.空间效率:空间利用率低,可用内存缩小为原来的一半;

2.复制成本问题:如果要清除的垃圾比较少,有效对象比较多,复制成本比较大。


2.3 标记-整理算法

       标记-整理算法(Mark-and-Compact)是一种解决复制算法缺点的内存回收方法。它与"标记-清理"算法在标记过程上相同,但不同之处在于处理存活对象的方式。标记-清理算法会直接回收垃圾对象,而标记-整理算法则是将所有存活对象向一端移动,然后直接清理掉垃圾对象。这种方法类似于顺序表中删除中间元素时的元素搬运操作。

       虽然标记-整理算法解决了内存碎片问题,同时也保证了空间利用率问题,但是它也存在明显的问题:

 效率不高,如果要搬运的空间比较大,此时开销比较大


2.4 分代回收算法

上述算法各有优缺点,我们可以根据不同的场景应用不同的算法,从而得出了一个复合算法——分代回收算法。该算法将垃圾回收分为不同的场景,根据对象的生命周期长短来选择使用哪种算法。

Java的对象要么生命周期特别短,要么生命周期特别长。我们可以引入一个概念,即对象的年龄。年龄是指对象熬过垃圾回收(GC)的轮次。经过一次垃圾回收的扫描,如果发现这个对象还不是垃圾,就为它增加一轮年龄。

      对于年轻代的对象,我们可以使用复制算法或标记-整理算法进行垃圾回收;而对于老年代的对象,我们可以使用标记-清理算法或标记-整理算法进行垃圾回收。

      如上图,根据对象的年龄,我们将对象分成不同的代。新创建的对象,其年龄为0,被放置在伊甸区(Eden Space)。在年轻代中,我们使用复制算法来进行垃圾回收。当对象在垃圾回收过程中存活下来,即熬过一轮GC后,它会被移动到幸存区(Survivor Space)。

      在幸存区,对象会继续经历周期性的垃圾回收扫描。如果对象被识别为垃圾,则会被释放;如果不是垃圾,它会被拷贝到另一个幸存区。注意,两个幸存区不会同时使用,而是交替使用。由于幸存区的空间相对较小,这种来回拷贝导致的空间浪费是可以接受的。

      如果一个对象在两个幸存区之间多次来回拷贝,它的年龄会逐渐增加,最终会被移至老年代(Old Generation)。老年代中的对象通常具有较长的生命周期。对于老年代,我们也需要进行周期性的垃圾回收扫描,但相比年轻代,频率更低。在老年代,我们采用标记-整理算法来释放空间。

       通过这种分代回收的方式,我们可以更高效地管理内存,对不同生命周期的对象采用适当的垃圾回收策略,从而优化程序的性能和内存使用效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值