细说JVM的垃圾回收机制

什么是垃圾回收?

从字面看来,按字面意思来理解就是--找到垃圾对象并将他们抛弃掉;事实却正好相反,垃圾回收是把处于活动状态的对象找出来,而将剩余的对象标记为垃圾对象。基于此理论,我们来详细描述java virtual machine的自动垃圾回收机制。

我们首先从最基础的垃圾回收的一些属性、概念、方法讲起,而不会直奔主题:JVM的垃圾回收机制。

声明:  这边文章主要论述Oracle HotSpot和OpenJDK的垃圾回收机制。至于其它的一些JVM,例如JRockit或者IBM J9,它们可能采用了不同机制来实现垃圾回收。

手动管理内存(Mannual memory management)

在介绍现代的自动垃圾回收机制之前,先让我们回到手动为数据分配内存,然后再手动回收这些内存的时代吧。在这个年代,如果你忘记了释放这些已分配的内存,那么就不能再重新使用这些内存空间了。这些内存虽然已经被声明进行了分配,但是确不能再使用,我们管这样的场景就叫:内存泄漏

下面是一段用C语言开发的手动管理内存的代买:

int send_request() {
    size_t n = read_size();
    int *elements = malloc(n * sizeof(int));

    if(read_elements(n, elements) < n) {
        // elements not freed!
        return -1;
    }

    // …

    free(elements)
    return 0;
}

从代码当中我们可以看出,很容易忘记去释放内存,从而很容易发生内存泄漏问题。发生内存泄漏以后,我们只能通过排查代码的方式找到具体的原因。所以,最好的方式就是有一种机制,自动的去回收不再使用的内存,从而降低人为错误的概率。这种自动回收内存的机制就叫Garbage Collection(简称:GC)。

智能指针

实现自动化垃圾回收的最直接的一种方式就是利用destructor。例如,我们可以利用C++里得vector来实现,当变量从vector脱离以后,destructor就会自动被调用,从而回收内存:

int send_request() {
    size_t n = read_size();
    vector<int> elements = vector<int>(n);

    if(read_elements(elements.size(), &elements[0]) < n) {
        return -1;
    }

    return 0;
}

但是在一些复杂的场景中,特别是共享对象被多线程同时引用的时候,单单利用destructor明显不能满足需求。基于以上的场景,最简单的方式就是:对对象的被引用进行计数。针对每个对象,我们会记录下这个对象当下被引用的次数是多少,当被引用的次数变为0的时候,表明这个对象占用的内存就可以被回收了。很著名的一个实现就是利用C++的共享指针了:

int send_request() {
    size_t n = read_size();
    auto elements = make_shared<vector<int>>();

    // read elements

    store_in_cache(elements);

    // process elements further

    return 0;
}

为了避免函数在被调用的时候,元素再一次被读取,我们可以将其放进缓存,在这种场景下,利用vector来进行对象销毁就不可行了。因此,我们改为利用shared_ptr,它将持续关注元素的被引用数,当指针在其它地方被引用的时候,引用数就会加一,当指针被释放的时候,引用数就会减一,当引用数被减为0时,shared_ptr就是删除所关联的vector。

自动化内存管理(Automated memory management

观察上面的C++代码,我们可以明确什么时候需要关心内存的管理。但是,我们如果将这种机制应用到所有的对象上面将会怎么样?这将使得我们的工作变得非常顺手,因为,作为开发者,再也不用关心垃内存回收的问题了。在运行的过程当中,将自动检测出不再被使用的对象,并清除它们以便释放内存空间。换句话说,就是自动进行垃圾回收。第一个垃圾回收器是LISP语言的,诞生于1959年,以后的日子了,这项技术在不断的进步。

引用计数法(reference counting)

很多的语言,例如Perl、PHP、Python都是采用我们上文提到的C++共享指针的方式进行垃圾回收。通过下图可能展示的更清晰:

绿色的云区域表示被他们所引用的对象还在被程序所使用,这些对象可能是当前运行方法里的本地变量,也可能为一些静态变量,或者其它的一些对象。每种语言与每种语言在这方面可能会有不同,但这些不是重点。

蓝色的链路代表内存中的活跃对象调用链路,圆圈的数字标识了这个对象当前被引用的次数。灰色的链条代表了那些没有再被显示引用到的对象(指得是那些被绿色的云引用到的对象)。灰色的对象就是可以被垃圾回收器回收的垃圾对象了。

这个方法看起来真的很好,但是却有一个致命的缺陷,就是会产生分离的垃圾闭环,上面的对象实际上已经没有意义了,但是它们的被引用数又都不为零。如下图所示:

图中的红色闭环,实际上已经成为了应用程序不再使用的垃圾对象,但是由于被引用数不为零,所以不能被回收,从而造成了内存泄漏的问题。

有很多的办法可以解决这个问题,例如可以利用弱引用的方式(‘weak’reference),也可以采用单独的算法对闭环链路进行垃圾回收。 我们上文提到的Perl、Python、PHP都会采用其中的一种方式来进行垃圾回收。 我们将不会对它们的实现进行详细描述,而是在后续的章节当中,对JVM的机制进行详细解读。

猜你喜欢

转载自blog.csdn.net/ppvqq/article/details/79989662