转载

JVM之垃圾回收(一)

乍一看,垃圾收集应该处理名称所暗示的内容——查找和丢弃垃圾。实际上,它所做的恰恰相反:垃圾收集跟踪所有仍在使用的对象,并将其余对象标记为垃圾。有了这个概念,我们开始深入研究Java虚拟机(JVM)垃圾收集(GC)所涉及自动内存回收的相关细节。

人工管理内存

学过C/C++的都知道,您必须手动并显式地为数据分配和释放内存。如果忘记释放它,你将无法重用内存,这些内存将被声明但不再被使用。这种情况称为内存泄漏。

下面是个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;
}
复制代码

如上文所示,很容易忘记释放内存。内存泄漏是一个很困扰开发者的问题。你只能通过修改你的代码来修复它们,但是在大型系统中,这将变得十分困难。因此,更好的方法是自动回收未使用的内存,完全消除人为出错的可能性。这种自动化称为垃圾收集(简称GC)。

智能指针

第一个自动化这种过程的方法是使用析构函数。例如,我们可以在c++中使用vector做同样的事情,当析构函数不在作用域中时,它将被自动调用:

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;
}
复制代码

但是在更复杂的情况下,特别是在多个线程共享对象时,仅使用析构函数是不够的。解决的办法是:引用计数(reference counting):对于每个对象,您只需知道它被引用了多少次,以及当这个计数达到零时,可以安全地回收该对象。一个著名的例子就是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;
}
复制代码

为了避免在下次调用函数时读取元素,我们可能需要缓存它们。在这种情况下,在向量超出范围时销毁它不是一个选项。因此,我们使用shared_ptr。它跟踪对它的引用的数量。这个数字随着您传递它而增加,随着它离开范围而减少。一旦引用的数量达到零,shared_ptr就会自动删除底层向量。

自动内存管理

在上面的c++代码中,我们仍然需要明确地指出何时需要进行内存管理。但如果我们能让所有对象都这样呢?这将非常方便,开发人员不再需要考虑自己清理之后的问题。程序运行时将自动理解不再使用的某些内存并释放它。换句话说,它自动收集垃圾。第一个垃圾收集器是在1959年为Lisp创建的,从那时起该技术才开始发展。

引用计数

我们用c++的共享指针演示的思想可以应用于所有对象。许多语言,如Perl、Python或PHP都采用这种方法。如下图:

JVM之垃圾回收(一)

绿色的表示它们所指向的对象仍然在被程序员使用。从技术上讲,这些可能是当前执行方法中的局部变量或静态变量或其他东西。它可能会因编程语言的不同而有所不同,所以我们不在这里集中讨论它。

蓝色圆圈是内存中的活动对象,其中的数字表示它们的引用计数。

最后,灰色圆圈是未从任何仍显式使用的对象引用的对象(这些对象由绿色云直接引用)。

JVM之垃圾回收(一)

看到了吗?红色对象实际上是应用程序不使用的垃圾。但是由于引用计数的限制,仍然存在内存泄漏。

有一些方法可以克服这个问题,比如使用特殊的“弱”引用或应用一个单独的算法来收集循环。前面提到的语言——Perl、Python和PHP——都以这样或那样的方式处理循环,但这超出了本手册的范围。相反,我们将开始更详细地研究JVM采用的方法。

标记+清除

首先,JVM对对象的可达性的构成更加具体。与我们在前几章中看到的模糊定义的绿云不同,我们有一组非常具体和明确的对象,称为GC Roots:

  • 局部变量
  • 活动线程
  • 静态变量
  • JNI引用

JVM跟踪所有可访问(活动)对象,并确保不可访问对象声明的内存可以重用的方法称为标记和清除算法。它包括两步:

  • 标记:遍历所有可访问对象,从GC Roots开始,并在本机内存中保存关于所有此类对象的记账簿
  • 清除:确保不可访问对象占用的内存地址可以被下一次分配重用。

JVM中不同的GC算法(如并行清除、CMS)实现这些标记+清除的方式略有不同,但是在概念类似。

这种方法至关重要的一点是,环形引用不再泄露:

JVM之垃圾回收(一)

不太好的一点是,为了垃圾回收的进行,应用程序线程需要停止:因为如果引用一直在更改,对象就没法真正计数。当应用程序暂时停止,以便JVM可以专注于垃圾回收时,这种情况称为“Stop The Word”(STW)。STW发生的原因有很多,但是垃圾收集是目前最流行的一种。

参考链接: plumbr.io/handbook/wh…

原文  https://juejin.im/post/5d208f5de51d45106b15ff90
正文到此结束
Loading...