譯文-垃圾回收器是什麼

原文出處: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「」。它們可能由於多種緣由發生,可是這種垃圾回收器是最主流的一種。

相關文章
相關標籤/搜索