JVM中的GC垃圾回收机制,引用计数法、复制算法和标记压缩清除算法

目录

一、GC是什么

二、引用计数法详解

三、复制算法详解

四、标记压缩清除算法


 

一、GC是什么

JVM中的GC指的是垃圾回收(Garbage Collection)。在Java虚拟机中,程序运行时会产生很多对象,这些对象可能会被程序不再使用,但它们仍然占据着内存空间,如果不及时清理,就会导致内存泄漏或内存溢出等问题。因此,Java虚拟机会通过垃圾回收器对这些无用对象进行自动清理和释放,以保证程序能够正常运行并且不会出现内存相关的问题。垃圾回收器可以根据不同的算法和策略来实现垃圾回收,包括标记-清除算法、复制算法、标记-整理算法等。

 

二、引用计数法详解

JVM中的引用计数法是一种垃圾回收算法。它的基本思想是通过记录每个对象被引用的次数,当引用该对象的变量被销毁时,将该对象的引用计数减1;如果发现某个对象的引用计数为0,则说明该对象已经不再被使用,可以进行垃圾回收了。

然而,由于引用计数法无法处理循环引用的情况,即两个或多个对象互相引用形成一个环,因此在实际应用中很少使用这种垃圾回收算法。目前主流的垃圾回收算法包括标记-清除算法、复制算法、标记-整理算法等。

下面是一个C++的引用计数法代码示例,供参考:

#include <iostream>
using namespace std;

class ReferenceCountedObject
{
public:
    int reference_count;
    ReferenceCountedObject() : reference_count(0) {}
    virtual ~ReferenceCountedObject() {}
};

class SomeClass : public ReferenceCountedObject
{
public:
    SomeClass() { cout << "SomeClass constructed" << endl; }
    ~SomeClass() { cout << "SomeClass destroyed" << endl; }
};

int main()
{
    SomeClass* obj1 = new SomeClass();
    obj1->reference_count++;
    SomeClass* obj2 = obj1;
    obj2->reference_count++;

    if (obj1->reference_count == 2)
    {
        delete obj1;
    }

    if (obj2->reference_count == 2)
    {
        delete obj2;
    }

    return 0;
}

这个示例通过定义一个ReferenceCountedObject类来维护每个对象的引用计数,SomeClass类继承自ReferenceCountedObject类并实现具体功能。在main函数中,我们创建了两个指向同一个对象的指针,并对它们进行了引用计数。当其中一个指针被销毁时,我们检查其所指向的对象的引用计数是否为0,如果是,则说明该对象已经不再被引用,可以进行垃圾回收。

 

三、复制算法详解

JVM的复制算法是一种垃圾回收算法,它主要应用于新生代的垃圾回收。在堆内存中,将新生代分为两个相等大小的区域:一个是Eden区,一个是Survivor区(通常有两个Survivor区)。当对象被创建时,会被放入Eden区,如果经过一次Minor GC后,仍然存活的对象会被移动到Survivor区中的一个,并且它们的年龄加1。当Survivor区空间不足时,剩余存活对象会被移动到另一个Survivor区中,并且它们的年龄重置为1。

复制算法的名称来源于其基本思想,即将存活的对象从一个区域复制到另一个区域。这样做的好处是可以避免内存碎片的产生,而且复制算法在进行垃圾回收时非常高效,因为只需要对存活对象进行复制和移动就可以了,不需要扫描整个堆空间。

然而,复制算法也有一些限制,例如需要至少两倍的空间来存放所有存活对象,如果存活对象很多,则可能导致堆空间不足。此外,由于每次垃圾回收都需要将存活对象复制到另一个区域,所以频繁进行垃圾回收会导致复制的开销很大。因此,复制算法主要应用于新生代,而老年代则通常采用其他垃圾回收算法。

JVM的复制算法是一种基于分代的垃圾回收算法,其主要思想是将堆内存分为两个区域:一个存活对象区和一个空闲对象区。在进行垃圾回收时,首先将存活对象从存活对象区复制到空闲对象区,然后清空存活对象区中的所有对象,使其变为空闲对象区。这样,在下一次垃圾回收时,就可以直接使用空闲对象区,而不需要再去扫描存活对象区。

下面是一个简单的JVM复制算法的示例代码:

public class CopyingGC {
  private static final int _1MB = 1024 * 1024;

  public static void main(String[] args) {
    byte[] allocation1, allocation2, allocation3, allocation4;
    allocation1 = new byte[2 * _1MB];
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation4 = new byte[4 * _1MB];  
  }
}

在上面的示例中,我们定义了4个byte数组,并依次分配了8MB、8MB、8MB、16MB的内存。在进行垃圾回收时,JVM会将这些存活对象从存活对象区复制到空闲对象区,并将存活对象区清空。如果对比存活对象区和空闲对象区的内存占用情况,我们会发现存活对象区的内存占用量大约是8MB,而空闲对象区的内存占用量为0。这说明JVM通过复制算法将存活对象从存活对象区复制到空闲对象区,并且成功地回收了不再使用的内存。

 

四、标记压缩清除算法

JVM的标记压缩清除算法是一种垃圾回收算法,也称为“标记-压缩算法”或“标记-整理算法”。它在标记-清除算法的基础上进行了改进,解决了标记-清除算法中会产生内存碎片的问题。

标记-压缩清除算法的基本流程如下:

1.首先和标记-清除算法一样,从根节点开始遍历所有可达对象,将其标记为“活动对象”。

2.然后,将所有活动对象压缩到堆的一端,也就是将它们移动到连续的地址空间中。这个过程也称为“压缩”或“整理”,相当于把所有活动对象都移到了一起,以便在后续的内存分配时能够使用较大的连续块。

3.最后,对堆的另一端的未被使用的内存进行回收。

相比标记-清除算法,标记-压缩清除算法的优势在于可以减少内存碎片的产生,提高内存的利用率,在应对长时间运行的程序时,效果尤为明显。但同时,由于需要移动对象,所以它的性能开销较大。

首先,一般而言,Java程序员不会直接编写JVM的垃圾回收器。相反,他们使用Java的内存管理机制和垃圾回收器作为后台服务来管理内存。Java垃圾回收器根据堆的大小、内存使用情况等因素来决定何时触发垃圾回收,并根据所选的垃圾回收算法进行垃圾回收。以下是一个简单的Java代码片段,演示了标记压缩清除算法:

public class MyGarbageCollector {
    
    public void collectGarbage(Heap heap) {
        // 标记活跃对象
        markActiveObjects(heap);
        
        // 压缩所有对象
        compactObjects(heap);
        
        // 清除未标记对象
        clearUnmarkedObjects(heap);
    }
    
    private void markActiveObjects(Heap heap) {
        // 遍历所有对象,将可达的对象打上标记
        for(Object object : heap.getAllObjects()) {
            if(isObjectReachable(object)) {
                object.setMarked(true);
            }
        }
    }
    
    private void compactObjects(Heap heap) {
        int offset = 0;
        // 遍历所有对象,将被标记的对象移动到堆的起始位置,并更新引用的指针
        for(Object object : heap.getAllObjects()) {
            if(object.isMarked()) {
                object.setOffset(offset);
                offset += object.getSize();
            }
        }
    }
    
    private void clearUnmarkedObjects(Heap heap) {
        // 遍历所有对象,将未被标记的对象从堆中释放
        for(Object object : heap.getAllObjects()) {
            if(!object.isMarked()) {
                heap.free(object);
            } else {
                // 清除标记,以便下次GC使用
                object.setMarked(false);
            }
        }
    }
    
    private boolean isObjectReachable(Object object) {
        // 判断对象是否可达
        // ...
    }
}

在这个示例代码中,我们使用了 markActiveObjects 方法来遍历所有对象,并将可达的对象打上标记。接下来,在 compactObjects 方法中,我们移动了所有被标记的对象到堆的起始位置,并更新引用的指针。最后,在 clearUnmarkedObjects 方法中,我们遍历所有对象,将未被标记的对象从堆中释放,并清除标记以便下次垃圾回收使用。

当然,这是一个非常简单的实现,实际上,Java虚拟机和垃圾回收器的实现要复杂得多,而且包含了很多其他的优化和特性。

 

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨荧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值