[Understanding Java Garbage Collection]理解Java垃圾收集(一)

Understanding Java Garbage Collection

理解Java垃圾收集

原文鏈接:http://www.cubrid.org/blog/dev-platform/understanding-java-garbage-collection/


瞭解Java中GC的工作機制有什麼好處?除了滿足軟件工程師的求知慾之外,瞭解GC工作機制還能幫助你寫出更好的Java程序。

這是我自己非常個人和主觀的看法,但是我相信一個精通GC的人可以成爲更好的Java開發者。如果你對GC流程感興趣,這意味着你在開發特定大小應用程序方面有經驗。如果你仔細思考過選擇正確的GC算法,這意味着你完全瞭解你開發的應用程序的特點。當然,這不是成爲好開發者的通用標準。但是,當我說理解GC是成爲偉大Java開發者所必需的時候,沒有人會反對。

這是“成爲Java GC專家”系列文章的第一篇。這次我將涵蓋GC的介紹,下次我會討論分析GC的狀態和NHN公司的GC優化示例。

這篇文章的目的是以一種輕鬆的方式把GC介紹給你。我希望這篇文章會有幫助。事實上,我的同事已經發表了一些關於Java Internals的文章,它們再推特上很受歡迎。你也可以參考一下。

回到GC上來,你在學習GC之前,應該知道一個術語。這個術語是”stop-the-world“。無論你選擇哪種GC算法,stop-the-world都會出現。stop-the-world意思是,JVM要停止應用程序來運行GC。當stop-the-world出現時,除了GC線程,其他所有的線程都會停止它們的任務。這些中斷的任務將在GC任務完成之後繼續運行。GC優化經常意味着減少stop-the-world的時間。

分代垃圾收集

Java在程序代碼中並不顯示分配內存和回收內存。有些人會設置引用的對象爲null,然後使用System.gc()方法來顯示回收內存。設置對象爲null不是一個大問題,但是調用System.gc()方法會嚴重影響系統的性能,最好不要這麼做。(幸好我還沒有見到過NHN的哪個開發者調用過這個方法)。

在Java中,既然開發者不顯示分配或回收內存,垃圾收集器發現不需要的(垃圾)對象時就會回收它們。垃圾收集器基於下面兩個假設創建。(稱它們爲前提假設或者先決條件而非假設更合適。)

  • 大多數對象將不可達。
  • 從老對象指向新生對象的引用很少。

這些假設叫做weak generational hypothesis。因此,爲了確保假設強化,在HotSpot VM中物理的分爲新生代和老年代。

新生代:大多數新創建的對象被分配到此。因爲大多數對象將不可達,許多對象在新生代區被創建,然後消失。當對象從這個區域回收時,我們稱“minor GC”發生了。

老年代:不可達的對象以及從新生代區存活下來的對象將被拷貝到此。這個區域比新生代區一般要大。因爲更大,所以在這裏比在新生代區更少發生GC。當對象從老年代消失時,我們稱“major GC”(或者“full GC”)發生了。

我們通過圖來看一下。

java-gc-area-data-flow.png
圖1 GC區域和數據流

上圖中的永久代叫做“方法區”,他存儲着類和字符串常量。所以這裏不是從老年代存活下來的對象保持持久的地方。這裏也可能發生GC。GC發生的GC也叫major GC。

有人可能存在疑問:

如果一個老年代的對象需要引用一個新生代的對象會怎麼樣?

爲了解決這個問題,在老年代區有一個叫做“card table”的東西,它是一個512字節的塊。當一個老年代的對象引用新生代的對象時,就會被記錄到此。當新生代執行GC時,只有這個搜索這個card table來確定是否回收,而不用檢查老年代所有對象的引用。這個card table由write barrier管理。write barrier是提高minor GC性能的一種方式。儘管這會帶來一定的開銷,但是整個GC的時間減少了。

card-table-structure.png
圖2 Card Table結構

新生代的組成

爲了理解GC,我們來看一下新生代,對象最先被創建在這裏。新生代可以分爲3個區域。

  • 一個Eden區域
  • 兩個Survivor區域

總共有3塊區域,其中兩個是Survivor區域。每個區域的執行流程如下:

  1. 新創建的大部分對象都被分配在Eden區域。
  2. Eden區域執行一次GC之後,存活的對象會被移動到Survivor區域的一箇中。
  3. Eden區域執行一次GC之後,對象被堆積到Survivor區域,無論其他存活對象是否已經存在。
  4. 一旦Survivor區域滿了,存活的對象會被移動到另一個Survivor區域。然後,滿的Survivor區域會變成沒有數據的狀態。
  5. 重複多次這些操作之後存活的對象會被移動到老年代區。

通過檢查這些步驟,你可以看到,其中一個Survivor區域必須保持空的。如果兩Survivor區域都有數據,或者使用率都是0,那麼很遺憾你的系統一定出了什麼問題。

數據通過minor GC轉移到老年代的過程如下圖所示:

before-and-after-java-gc.png
圖3 GC前後

注意在HotSpot VM中,使用了兩種可以更快分配內存的技術。一個叫做“bump-the-pointer”,另一個叫做“TLABs (Thread-Local Allocation Buffers)”。

Bump-the-pointer技術跟蹤最後分配到Eden區域的對象。這個對象存在Eden區域的頂部。如果之後又有對象被創建,它會檢查對象的大小是否適合Eden區域。如果看起來合適,就會被放到Eden區域,然後對象放到頂部。因此,當新對象被創建時,只有最後添加的對象需要被檢查,這就會有更快的內存分配。但是,如果考慮多線程環境,這就不一樣了。爲了在Eden區域爲線程安全保存多個線程的對象,必須要加鎖,從而導致因鎖競爭而引起性能的下降。TLABs是HotSpot中解決這個問題的方法。它要求每個線程都有在Eden區域都有一塊獨享的空間。因爲每個線程只能訪問自己的TLAB,就算是bump-the-pointer技術也允許不加鎖的內存分配。

以上是GC在新生代的簡介。你不需要記住我剛提到的那兩個技術。也不需要刻意去記住它們。但是要記住對象最開始在Eden區域創建,長時間存活的對象通過Survivor區域移動到老年代。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章