對於Java開發人員來講,瞭解垃圾回收機制(GC)有哪些好處呢?首先能夠知足做爲一名軟件工程師的求知慾,其次,深刻了解GC如何工做能夠幫你寫出更好的Java應用。 程序員
這僅僅表明我我的的意見,但我堅信一個精通GC的人每每是一個好的Java開發者。若是你對GC的處理過程感興趣,說明你已經具有較大規模應用的開發經驗。若是你曾經想過如何正確的選擇GC算法,那意味着你已經徹底理解你所開發的應用的特色。固然,咱們不能以偏概全,這不能做爲評價一個好的開發人員的共通標準。可是,我要說的是,深刻理解GC是成爲一名偉大的程序員的必經之路。 算法
這是成爲JavaGC專家系列文章的第一篇,本篇主要針對GC機制進行介紹,在下一篇中,咱們將重點探討分析GC狀態以及來自NHN的GC調優的例子。 安全
本文的目的是以一種簡單的方式向你介紹GC機制。我但願這些文章可以幫到你。實際上,個人學生已經在Twitter上發佈了一些很好的關於Java內核的文章,而且大受歡迎。有興趣的話,你也能夠關注他們。 性能優化
回到正題,我們繼續談垃圾回收,在學習GC以前,你首先應該記住一個單詞:「stop-the-world」。Stop-the-world會在任何一種GC算法中發生。Stop-the-world意味着 JVM 由於要執行GC而中止了應用程序的執行。當Stop-the-world發生時,除了GC所需的線程之外,全部線程都處於等待狀態,直到GC任務完成。GC優化不少時候就是指減小Stop-the-world發生的時間。 服務器
按代的垃圾回收機制 多線程
在Java程序中不能顯式地分配和註銷內存。有些人把相關的對象設置爲null或者調用System.gc()來試圖顯式地清理內存。設置爲null至少沒什麼壞處,可是調用System.gc()會顯著地影響系統性能,必須完全杜絕(還好,我尚未見到NHN的哪一個開發者調用這個方法)。 性能
在Java中,開發人員沒法直接在程序代碼中清理內存,而是由垃圾回收器自動尋找沒必要要的垃圾對象,而且清理掉他們。垃圾回收器會在下面兩種假設(hypotheses)成立的狀況下被建立(稱之爲假設不如改成推測(suppositions)或者前提(preconditions))。 學習
這些假設咱們稱之爲弱年代假設( weak generational hypothesis)。爲了強化這一假設,HotSpot虛擬機將其物理上劃分爲兩個–新生代(young generation)和老年代(old generation)。
新生代(Young generation): 絕大多數最新被建立的對象會被分配到這裏,因爲大部分對象在建立後會很快變得不可到達,因此不少對象被建立在新生代,而後消失。對象從這個區域消失的過程咱們稱之爲」minor GC「。 測試
老年代(Old generation): 對象沒有變得不可達,而且重新生代中存活下來,會被拷貝到這裏。其所佔用的空間要比新生代多。也正因爲其相對較大的空間,發生在老年代上的GC要比新生代少得多。對象從老年代中消失的過程,咱們稱之爲」major GC「(或者」full GC「) 優化
請看下面這個圖表。
圖1 : GC 空間 & 數據流
上圖中的持久代( permanent generation )也被稱爲方法區(method area)。他用來保存類常量以及字符串常量。所以,這個區域不是用來永久的存儲那些從老年代存活下來的對象。這個區域也可能發生GC。而且發生在這個區域上的GC事件也會被算爲major GC。
有些人可能會問:
若是老年代的對象須要引用一個新生代的對象,會發生什麼呢?
爲了解決這個問題,老年代中存在一個」card table「,他是一個512 byte大小的塊。全部老年代的對象指向新生代對象的引用都會被記錄在這個表中。當針對新生代執行GC的時候,只須要查詢card table來決定是否能夠被收集,而不用查詢整個老年代。這個card table由一個write barrier來管理。write barrier給GC帶來了很大的性能提高,雖然由此可能帶來一些開銷,但GC的總體時間被顯著的減小。
圖 2: Card Table 結構
新生代的構成
爲了更好地理解GC,咱們如今來學習新生代,新生代是用來保存那些第一次被建立的對象,他能夠被分爲三個空間
一共有三個空間,其中包含兩個倖存者空間。每一個空間的執行順序以下:
若是你仔細觀察這些步驟就會發現,其中一個倖存者空間必須保持是空的。若是兩個倖存者空間都有數據,或者兩個空間都是空的,那必定標誌着你的系統出現了某種錯誤。
經過頻繁的minor GC將數據移動到老年代的過程能夠用下圖來描述:
圖 3: GC執行先後對比
須要注意的是HotSpot虛擬機使用了兩種技術來加快內存分配。他們分別是是」bump-the-pointer「和「TLABs(Thread-Local Allocation Buffers)」。
Bump-the-pointer技術跟蹤在伊甸園空間建立的最後一個對象。這個對象會被放在伊甸園空間的頂部。若是以後再須要建立對象,只須要檢查伊甸園空間是否有足夠的剩餘空間。若是有足夠的空間,對象就會被建立在伊甸園空間,而且被放置在頂部。這樣以來,每次建立新的對象時,只須要檢查最後被建立的對象。這將極大地加快內存分配速度。可是,若是咱們在多線程的狀況下,事情將大相徑庭。若是想要以線程安全的方式以多線程在伊甸園空間存儲對象,不可避免的須要加鎖,而這將極大地的影響性能。TLABs 是HotSpot虛擬機針對這一問題的解決方案。該方案爲每個線程在伊甸園空間分配一塊獨享的空間,這樣每一個線程只訪問他們本身的TLAB空間,再與bump-the-pointer技術結合能夠在不加鎖的狀況下分配內存。
以上是針對新生代空間GC技術的簡要介紹,你不須要刻意記住我剛剛提到的兩種技術。不知道他們不會對你產生什麼影響,可是請務必記住在對象剛剛被建立以後,是保存在伊甸園空間的。那些長期存活的對象會經由倖存者空間轉存在老年代空間。
老年代GC處理機制
老年代空間的GC事件基本上是在空間已滿時發生,執行的過程根據GC類型不一樣而不一樣,所以,瞭解不一樣的GC類型將有助於你理解本節的內容。
JDK7一共有5種GC類型:
其中,Serial GC不該該被用在服務器上。這種GC類型在單核CPU的桌面電腦時代就存在了。使用Serial GC會顯著的下降應用的性能指標。
如今,讓咱們共同窗習每一種GC類型
1. Serial GC (-XX:+UseSerialGC)
新生代空間的GC方式咱們在前面已經介紹過了,在老年代空間中的GC採起稱之爲」mark-sweep-compact「的算法。
最後一步,從頭開始,順序地填滿堆內存空間,而且將對內存空間分紅兩部分:一個保存着對象,另外一個空着(壓縮)。
2. Parallel GC (-XX:+UseParallelGC)
圖 4: Serial GC 與 Parallel GC的區別
從上圖中,你能夠輕易地看出serial GC和parallel GC的區別,serial GC只使用一個線程執行GC,而parallel GC使用多個線程,所以parallel GC更高效。這種GC在內存充足以及多核的狀況下會頗有用,所以咱們也稱之爲」throughput GC「。
3. Parallel Old GC(-XX:+UseParallelOldGC)
Parallel Old GC在JDK5以後出現。與parallel GC相比,惟一的區別在於針對老年代的GC算法。Parallel Old GC分爲三步:標記-彙總-壓縮(mark – summary – compaction)。彙總(summary)步驟與清理(sweep)的不一樣之處在於,其將依然倖存的對象分發到GC預先處理好的不一樣區域,算法相對清理來講略微複雜一點。
4. CMS GC (-XX:+UseConcMarkSweepGC)
圖 5: Serial GC & CMS GC
就像你從上圖看到的那樣, CMS GC比我以前解釋的各類算法都要複雜不少。第一步初始化標記(initial mark) 比較簡單。這一步驟只是查找那些距離類加載器最近的倖存對象。所以,停頓的時間很是短暫。在以後的並行標記( concurrent mark )步驟,全部被倖存對象引用的對象會被確認是否已經被追蹤和校驗。這一步的不一樣之處在於,在標記的過程當中,其餘的線程依然在執行。在從新標記(remark)步驟,會再次檢查那些在並行標記步驟中增長或者刪除的與倖存對象引用的對象。最後,在並行交換( concurrent sweep )步驟,轉交垃圾回收過程處理。垃圾回收工做會在其餘線程的執行過程當中展開。一旦採起了這種GC類型,由GC致使的暫停時間會極其短暫。CMS GC也被稱爲低延遲GC。它常常被用在那些對於響應時間要求十分苛刻的應用之上。
固然,這種GC類型在擁有stop-the-world時間很短的優勢的同時,也有以下缺點:
在使用這個GC類型以前你須要慎重考慮。若是由於內存碎片過多而致使壓縮任務不得不執行,那麼stop-the-world的時間要比其餘任何GC類型都長,你須要考慮壓縮任務的發生頻率以及執行時間。
5. G1 GC
最後,咱們來學習垃圾回收優先(G1)GC類型。
圖 6: G1 GC的結構
若是你想要理解G1,首先你要忘記你所學過的新生代和老年代的概念。正如你在上圖所看到的,每一個對象被分配到不一樣的格子,隨後GC執行。當一個區域裝滿以後,對象被分配到另外一個區域,並執行GC。這中間再也不有重新生代移動到老年代的三個步驟。這個類型是爲了替代CMS GC而被建立的,由於CMS GC在長時間持續運做時會產生不少問題。
G1最大的好處是性能,他比咱們在上面討論過的任何一種GC都要快。可是在JDK 6中,他還只是一個早期試用版本。在JDK7以後才由官方正式發佈。就我我的看來,NHN在將JDK 7正式投入商用以前須要很長的一段測試期(至少一年)。所以你可能須要再等一段時間。而且,我也聽過幾回使用了JDK 6中的G1而致使Java虛擬機宕機的事件。請耐心的等到它更穩定吧。
下一次我將討論GC優化相關的問題,可是在此以前我要先明確一件事情,假如應用中建立的全部對象的大小和類型都是統一的,那麼公司使用的WAS的GC參數能夠是相同的。可是WAS所建立對象的大小和生命週期根據服務以及硬件的不一樣而不一樣。換句話說,不能由於某個應用使用的GC參數「A」,就說明一樣的參數也能給其餘服務帶來最佳的效果。而是要因地制宜,有的放矢。咱們須要找到適合每一個WAS線程的參數,而且持續的監控和優化每一個設備上的WAS實例。這並非個人一家之談,而是負責Oracle Java虛擬機研發的工程師在 JavaOne 2010上已經討論過的。
本文中咱們簡略的介紹了Java的GC機制,請繼續關於咱們的後續文章,咱們將會討論如何監控Java GC狀態以及優化GC。
另外,我特別推薦一本2011年12月發佈的《Java性能》(Amazon,也能夠經過safari在線閱讀),還有在Oracle官網發佈的白皮書《Java HotSpotTM虛擬機內存管理》(這本書與Java性能優化不是同一本) 做者Sangmin Lee, NHN公司,性能工程師實驗室高級工程師。