译文-垃圾回收器是什么

原文出处:What Is Garbage Collection?算法

一眼就应该从名称看出垃圾回收机制的含义-查找垃圾,而后丢弃。事实正好相反。垃圾回收器追踪全部正在使用的对象,将无用对象标记为垃圾。请留意,咱们开始研究JVM的“Garbage Collection”的实现细节。缓存

避免仓促进入细节,咱们因该从入门开始入手。理解垃圾收集器的通常规律,核心、概念,处理方法。安全

郑重声明:本文关注的是Oracle的Hotspot和OpenJDK。其余运行环境或者其余的JVM,诸如jRockit,IBM J九、以及本手册说起的一些产品会有一些差别化。函数

内存管理指南spa

在咱们开始讲解垃圾回收器的工做方式以前,你须要手动为你的数据分配一块可用空间。若是你忘记分配,你将不能重复使用这块空间。这块空间将被声明可是不能被使用。例如,内存泄漏。线程

下面是一个用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)自动化操做。code

自动化指针对象

内存回收自动化的最好方式之一是使用钩子函数。例如,在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;
}

可是多数状况下更加复杂,特别是对象被多个线程跨线程共享,仅仅使用钩子函数不合适,由此产生最简单的垃圾回收机制:引用计数。对于每个对象,只要简单知道它被引用了多少次,当计数器归零的时候,它就被安全回收。下面是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。它记录引用次数。在它的做用域内数量增长,在做用域以外次数减小。一旦引用次数归零,shared_ptr 自动删除其下的vector。

内存自动管理

从上面的C++代码能够看出,咱们依然明确提出关注内存管理。可是若是使用这种方式管理全部的内存有会怎样呢?它变得很是方便,自此开发人员再也不考虑本身手动清除。运行环境会自动识别再也不使用的内存而且释放它。换句话说,它自动回收垃圾。世界上第一款垃圾收集于1959年使用Lisp语言实现,自此相关技术开始向前发展。

引用计数

上文提到咱们使用C++共享指针的方式管理全部对象。许多语言,例如 Perl, Python ,PHP都采用这种方式。下图很好说明了这种方法:

图片描述

绿色云状图标指向的对象说明他们仍然被程序占用。技术上,他们相似于当前执行方法的本地变量或者静态变量或者其余。不一样语言之间的差异很大,本文不作关注。

蓝色图标表示内存中存活的对象,里面的数字表明它们被引用的次数。最后,灰色图标表示没有被任何存活对象引用(就是直接被绿色云状表明对象引用的对象)。灰色对象就这样被当成垃圾,被垃圾收集器清除。

这看起来很不错,不是吗?好吧,的确,可是这种方式存在一个巨大的缺点。这些没有在做用域中对象发生环状引用,因为环状引用致使他们的引用计数不归零。下面是示意图:

图片描述

看到了吗?实际上,红色对象没有被应用程序使用而成为垃圾。因为引用计数的局限性,他们形成了内存泄漏。

有几种方法客服这种状况,例如使用特殊的“”weak“”引用,或者为了环状应用特殊的算法。Perl, Python ,PHP使用哪一种方式处理,不在本文的讨论范围以内。相反,我开始讨论更多JVM的实现细节。

标记清除

首先,对于可达对象,JVM有着明确的定义。与上面章节中绿色云状图模糊定义不一样的是,咱们有一个叫作Garbage Collection Roots(GC Root)的明肯定义:

  • 局部变量

  • 活跃线程

  • 静态域

  • JNI引用

JVM使用这种方式追踪全部可达(存活)的对象,经过被称做标记清除算法重复扫描肯定不可达对象。该算法包含两步:

  • 标记,扫描全部的可达对象,在本地内存这些对象的副本。

  • 清除,确保全部被不可达对象占据的内存空间能够在下一次从新分配。

JVM中多种不一样的算法,例如Parallel Scavenge,Parallel Mark+Copy,CMS,他们的实施阶段不尽相同,可是执行步骤和上面描述的两步相似。

这些算法很重要的一点是保证环形引用再也不泄漏:

图片描述

不太好的一点是,回收操做发生的时候,全部的应用线程被中止。正如它们一直在改变引用致使没法准确计算它们的应用的那样。像这种应用线程被暂时中止,以便JVM活动的晴空称之为“”stop-the-world“”。它们可能由于多种缘由发生,可是这种垃圾回收器是最主流的一种。

相关文章
相关标签/搜索