Java 記憶體回收機制概念梳理

來源:互聯網
上載者:User

標籤:

本文摘自我們幾周后即將出版的Garbage Collection Handbook一書的樣章。同時也讓你能熟悉下記憶體回收的基礎知識——這選自該書的第一章。

乍一看,記憶體回收所做的事情應當恰如其名——尋找並清除垃圾。事實上卻恰恰相反。記憶體回收會跟蹤所有仍在使用的對象,然後將剩餘的對象標記為垃圾。牢記了這點之後,我們再來深入地瞭解下這個被稱為“記憶體回收”的自動化記憶體回收在JVM中到底是如何?的。

手動管理記憶體

在介紹現代版的記憶體回收之前,我們先來簡單地回顧下需要手動地顯式分配及釋放記憶體的那些日子。如果你忘了去釋放記憶體,那麼這塊記憶體就無法重用了。這塊記憶體被佔有了卻沒被使用。這種情境被稱之為記憶體泄露

下面是用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)。

智能指標

自動記憶體回收早期的一種實現便是引用計數。你知曉每一個對象被引用了幾次,當計數器歸0的時候,這個對象就可以被安全地回收掉了。C++的共用指標就是一個非常著名的例子:

int send_request() {    size_t n = read_size();    stared_ptr<vector<int>> elements               = make_shared<vector<int>&gt();    if(read_elements(n, elements) < n) {        return -1;    }    return 0;}

我們使用的sharedptr會記錄這個對象被引用的次數。如果你將它傳遞給別人則計數加一,當它離開了範圍後便會減一。一旦這個計數為0,sharedptr會自動地刪除底層對應的vector。當然這隻是個樣本,因為也有讀者指出來了,這個在現實中是不太可能出現的,但作為示範是足夠了。

自動記憶體管理

在上面的C++代碼中,我們還得顯式地聲明我們需要使用記憶體管理。那如果所有的對象都採用這個機制會怎樣呢?那簡直就太方便了,這樣開發人員便無需 考慮清理記憶體的事情了。運行時會自動知曉哪些記憶體不再使用了,然後釋放掉它。也就是說,它自動地回收了這些垃圾。第一代的記憶體回收行程是1959年Lisp 引入的,這項技術迄今為止一直在不斷演化。

 

引用計數

 

剛才我們用C++的共用指標所示範的想法可以應用到所有的對象上來。許多語言比如說Perl, Python以及PHP,採用的都是這種方式。這個通過一張圖可以很容易說明:

 

綠色的雲代表的是程式中仍在使用的對象。從技術層面上來說,這有點像是正在執行的某個方法裡面的局部變數,亦或是靜態變數之類的。不同程式設計語言的情況可能會不一樣,因此這並不是我們關注的重點。

藍色的圓圈代表的是記憶體中的對象,可以看到有多少對象引用了它們。灰色圓圈的對象是已經沒有任何人引用的了。因此,它們屬於垃圾對象,可以被記憶體回收行程清理掉。

看起來還不錯對吧?沒錯,不過這裡存在著一個重大的缺陷。很容易會出現一些孤立的環,它們中的對象都不在任何域內,但彼此卻互相引用導致引用數不為0。下面便是一個例子:

看到了吧,紅色部分其實就是應用程式不再使用的垃圾對象。由於引用計數的缺陷,因此會存在記憶體泄露。

有幾種方法可以解決這一問題,比如說使用特殊的“弱”引用,或者使用一個特殊的演算法回收循環參考。之前提到的Perl,Python以及PHP等語言,都是使用類似的方法來回收循環參考的,不過這已經超出本文講述的範圍了。我們準備詳細介紹下JVM所採用的方法。

標記刪除

首先,JVM對於對象可達性的定義要明確一些。它可不像前面那樣用綠色的雲便含糊了事的,而是有著非常明確及具體的記憶體回收根對象(Garbage Collection Roots)的定義:

  • 局部變數
  • 活動線程
  • 靜態欄位
  • JNI引用
  • 其它(後面將會討論到)

JVM通過標記刪除的演算法來記錄所有可達(存活)對象,同時確保不可達對象的那些記憶體能夠被重用。這包含兩個步驟:

  • 標記是指遍曆所有可達對象,然後在本地記憶體中記錄這些對象的資訊
  • 刪除會確保不可達對象的記憶體位址可以在下一次記憶體配置中使用。

JVM中的不同GC演算法,比如說Parallel Scavenge,Parallel Mark+Copy, CMS都是這一演算法的不同實現,只是各階段略有不同而已,從概念上來講仍然是對應著上面所說的那兩個步驟。

這種實現最重要的就是不會再出現泄露的對象環了:

缺點就是應用程式的線程需要被暫停才能完成回收,如果引用一直在變的話你是無法進行計數的。這個應用程式被暫停以便JVM可以收拾家務的情況又被稱為Stop The World pause(STW)。這種暫停被觸發的可能性有很多,不過記憶體回收應該是最常見的一種。

 

Java 記憶體回收機制概念梳理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.