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”)發生了。
我們通過圖來看一下。
圖1 GC區域和數據流
上圖中的永久代叫做“方法區”,他存儲着類和字符串常量。所以這裏不是從老年代存活下來的對象保持持久的地方。這裏也可能發生GC。GC發生的GC也叫major GC。
有人可能存在疑問:
如果一個老年代的對象需要引用一個新生代的對象會怎麼樣?
爲了解決這個問題,在老年代區有一個叫做“card table”的東西,它是一個512字節的塊。當一個老年代的對象引用新生代的對象時,就會被記錄到此。當新生代執行GC時,只有這個搜索這個card table來確定是否回收,而不用檢查老年代所有對象的引用。這個card table由write barrier管理。write barrier是提高minor GC性能的一種方式。儘管這會帶來一定的開銷,但是整個GC的時間減少了。
圖2 Card Table結構
新生代的組成
爲了理解GC,我們來看一下新生代,對象最先被創建在這裏。新生代可以分爲3個區域。
- 一個Eden區域
- 兩個Survivor區域
總共有3塊區域,其中兩個是Survivor區域。每個區域的執行流程如下:
- 新創建的大部分對象都被分配在Eden區域。
- Eden區域執行一次GC之後,存活的對象會被移動到Survivor區域的一箇中。
- Eden區域執行一次GC之後,對象被堆積到Survivor區域,無論其他存活對象是否已經存在。
- 一旦Survivor區域滿了,存活的對象會被移動到另一個Survivor區域。然後,滿的Survivor區域會變成沒有數據的狀態。
- 重複多次這些操作之後存活的對象會被移動到老年代區。
通過檢查這些步驟,你可以看到,其中一個Survivor區域必須保持空的。如果兩Survivor區域都有數據,或者使用率都是0,那麼很遺憾你的系統一定出了什麼問題。
數據通過minor GC轉移到老年代的過程如下圖所示:
圖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區域移動到老年代。