java Hotspot 內存管理白皮書(中文翻譯)

1引言 html

一個健壯的 Java™2平臺,Standard Edition (J2SE™)擁有一個自動內存管理機制,它爲開發者們屏蔽了複雜的內存管理步驟。 前端

本文提供了一個關於java Hotspot 虛擬機中內存管理機制的簡單概述,它描述了一個可用於垃圾回收的內存管理器,而且提供了關於選擇和配置一個回收器以及設置內存區域大小的回收操做。它一樣能夠做爲一個參考書,本文列舉了與垃圾回收器行爲相關的一些最經常使用的方法,而且描述了他們之間千絲萬縷的關係。 java

第二章節中,咱們爲讀者展現最新的自動內存管理器的概念,在那裏咱們將討論手動爲數據分配內存空間對於程序員的好處。 linux

第三章節中,咱們將簡述一般的垃圾回收器的概念,設計的選擇,以及性能指標。本章節中,咱們還引入了稱之爲「代」的,一個經常使用的內存組織方式,它將基於對象的預期壽命來將不一樣的對象劃分到不一樣的區域中。這種按照代來劃分的方式已經被證實了可以有效地減小垃圾回收器的暫停時間和讓每個應用擁有一個良好的性價比。 程序員

在剩餘的章節中,咱們將會提供HotSpot的一些信息。在第四章節中,咱們將描述 javaSE 5.0 update 6中已經存在的4種垃圾回收器,和他們的內存管理方式還總結了每一種類型的垃圾回收器的最佳應用場合。 web

第五章節描述了javaSE 5.0 如何爲基於操做系統的,運行於虛擬機上的應用程序自動選擇垃圾回收器,設置堆大小,以及動態內存回收如何知足用戶的指望。咱們稱這個技術爲人體工程學。 算法

第六章提供了配置和選擇一個垃圾回收器的建議,而且提供了一些處理OutOfMemoryError異常的方法。 編程

第七章簡單介紹了一些用來評估垃圾回收器性能的工具。 windows

第八章列出了一些涉及到垃圾回收器的選擇,和行爲的經常使用的命令行選項。 數組

最後的第九章提供了一些本文涉及到的文檔的連接。

2手動VS自動內存管理

在內存管理過程當中,咱們認識到當咱們分配的對象已經再也不被使用的時候,咱們就要釋放這個對象所佔用的內存空間,(標記這塊內存區域)爲後續的內存分配讓出空間。在一些編程語言中,內存管理是程序員的職責(由程序員本身來管理內存的使用),可是因爲這個任務的複雜性會致使許多常見的錯誤,可能會致使意外或錯誤的程序行爲以及崩潰。基於以上的緣由,大部分的開發時間每每都耗費在調試和糾正這種錯誤上。

在手動管理內存的過程當中,一個空懸的引用一般會形成這樣的問題。當這個對象佔用的內存空間已經被釋放掉的時候,其餘的對象可能還擁有這個對象的引用。若是其餘的對象試圖訪問原始的對象,可是原來的空間已經被分配給新的對象,那麼結果就是不可預知的。

手動內存管理還存在另外一個常見的問題是內存泄漏,這些泄漏發生在內存已經被分配並再也不被引用可是卻沒有被釋放的時候。例如,若是你打算釋放一個鏈表所引用的全部空間,可是你錯誤地只是釋放了鏈表的第一個元素,剩下的列表元素雖然再也不被引用,可是他們卻離開了整個程序的控制範圍,他們所佔用的內存,既不可能被再次使用也不可能被回收。內存泄漏發生的時候,程序能夠繼續運行,直到耗盡全部可用的內存。

一種稱之爲垃圾回收器的自動內存管理程序目前已經成爲大多數現代面向對象的語言用來替代以前的內存管理方式。自動內存管理器提高了代碼的抽象性和接口性以及可靠性。

垃圾回收器避免了引用懸空的問題,由於一個被引用的對象將永遠不會被垃圾回收器收回,因此他所佔用的內存就不會被視爲空閒區。垃圾回收器也解決了內存泄漏的問題,由於它會自動釋放全部再也不被引用的對象所佔用的內存。

3垃圾回收器的概念

一個垃圾回收器的職責有:

分配內存

確保任何被引用的對象保留在內存中

釋放全部在執行的代碼中不可達的引用所指向的對象所使用的內存

咱們稱一個被引用的對象稱之爲存活的對象,一個再也不被引用的對象稱之爲死亡對象,並標記爲垃圾。一個爲對象尋找和釋放其所佔用的內存空間的過程,咱們稱之爲垃圾回收。

垃圾回收器只能解決一部分而不是全部的內存分配問題。例如,你能夠建立一個對象,無限期地引用他們,直到沒有更多可用的內存。垃圾回收器自己也是一個既佔用時間也消耗資源的複雜任務。

垃圾回收器處理着一個用於組織內存的分配和釋放空間的精確的算法,而且向程序員屏蔽掉這一個過程。空間一般來自於一個稱之爲堆的內存池中。

垃圾收集的時間由垃圾收集器決定,一般狀況下,當整個堆或者他的子集被填滿或達到了某個百分比佔用的閥值的時候會發生垃圾收集事件。

爲了分配請求的任務,尋找一塊具備必定規模的未使用是的內存塊是一個難題,最經常使用的內存分配算法的主要問題是避免內存碎片,同時保持高效地分配和釋放。

理想的垃圾收集器的特色

一個垃圾回收器應當是既安全又全面的。這意味着,存活的數據永遠不會被錯誤的釋放,

而且垃圾不該超過必定收集週期以後仍然沒有被收集。同時也意味着垃圾回收器應該是高效的,不該當使用應用程序的暫停來完成他的垃圾回收工做。

然而,在大多數實時操做系統中,時間,空間和頻率每每是相互取捨的,舉個例子,若是堆空間太小,收集工做會更快,可是堆也會被迅速地填滿,所以須要更頻繁的收集操做,相反地,一個足夠大的堆空間,須要更長的時間來填滿,所以收集頻率會更低,可是收集工做會耗費更多的時間。

理想的垃圾回收器另外一個的特色是碎片的限制。當內存上的垃圾對象被釋放的時候,釋放出的空閒區域可能會是一小塊一小塊的不連續空間,這樣會致使任何一個連續的區域中沒有一個區域有足夠的空間用於分配一個大的對象。有一種消除碎片的方法稱之爲壓縮,咱們將會在下面的各類垃圾回收器的設計中討論到。

可擴展性也很重要,在多處理器的操做系統上,分配操做不該當成爲多線程應用程序的瓶頸,收集操做也是如此。

設計選擇

當咱們設計或者選擇一個垃圾回收器算法的時候,咱們必須從如下選項中做出一個選擇。

串行或者並行:

在串行回收中,同一時間只能有一件事情發生。例如,即便當多個CPU能夠用的時候,也只有其中的一個用於執行回收操做。當咱們使用並行收集的時候,垃圾回收任務能夠分割成幾個部分,在不一樣的CPU中,同時並行執行這些子部分。併發操做能夠更迅速地完成收集到錯,不惜犧牲掉一些額外的複雜性和潛在的碎片。

併發或者停掉整個世界:

當經過「中止世界」的方式來運行垃圾回收器,垃圾回收器會在收集過程當中暫停應用程序。另外,一個或者多個垃圾回收器任務能夠併發執行,就是說能夠與應用程序一塊兒同時運行。一般狀況下,一個併發的垃圾回收器能夠併發執行大多數的工做,然是同時也可能偶爾須要作短時的「中止世界」的暫停操做。「中止世界」的垃圾回收器是簡單的併發回收器,由於堆在收集過程當中是被凍結且對象不能改變的,可是他的缺點是,咱們可能不但願某一些應用程序在運行過程當中被暫停。相應的,暫停時間較短的併發進行垃圾收集器,可是收集器必須格外當心,由於在同一時間,應用程序更新操做的對象和垃圾收集器操做的對象可能正在同時進行。這樣會增長併發回收器的運行開銷 ,這會影響到收集器的性能和須要更大的堆。

壓縮 VS 不壓縮 VS 拷貝:

在垃圾回收器已經決定哪些內存中的對象是活着的,哪些是垃圾以後,他就能夠開始壓縮內存了,移動全部存活的對象到一塊兒(移動到一個連續的空間中),而且徹底回收其他的內存。壓縮以後,咱們能夠很是容易和快速地將一個新的對象分配在第一個空閒的內存位置上,而且利用一個簡單的指針來跟蹤對象的下一個位置來做爲下一次分配的起始地點。與壓縮收集器相反,非壓縮收集器釋放垃圾對象所佔用的內存空間以後並不會像壓縮收集器同樣移動全部的存活對象來釋放一塊連續的空閒區域。這樣作的好處是能夠更快地完成垃圾收集,可是缺點是會形成潛在的碎片。一般來講,從內存就地釋放的堆中分配內存比已經壓縮過的堆中分配內存更昂貴。由於它必須從堆中搜索出一塊連續而且足夠大的內存來容納新的對象。

第三種方法就是拷貝回收器,他複製或者調整活着的對象到另外一個內存區域中,它的好處就是對象所在的舊內存區域能夠被認爲是空閒的內存區域,能夠很是快速和容易地進行後續的分配,但缺點就是拷貝舊內存區域中的對象到新的區域中須要額外的時間和空間。

性能指標:

利用幾個指標來估量垃圾回收器的性能,包括:

吞吐量—在一段足夠長的時間內,減去垃圾回收器時間後剩餘的時間與所佔整體時間的比例(不包含垃圾回收器的時間)

垃圾回收器的開銷—吞吐量的補數,也就是說,垃圾回收器所佔時間與總時間的比例。

暫停時間—應用程序執行過程當中,垃圾回收器執行時,應用程序暫定的時間。

回收效率--相對於應用程序的執行,回收操做發生的次數。

足跡—大小的度量單位,如堆大小。

迅速--一個對象變成垃圾和內存變成可用之間的時間

一個交互的應用程序可能須要短暫的暫停時間,然而對於非交互性的應用程序,總體的執行時間將會更重要。一個實時的應用程序會要求在任什麼時候期,垃圾回收器暫停時間和花費在收集過程當中的時間比例都要有一個最小的上限。一個足夠小的空間會成爲一個運行在小型我的電腦或者嵌入式系統的主要關注點。

代回收

當一個被稱之爲代回收的技術被使用的時候,內存將會根據代來劃分,也就是說,將不一樣年齡的對象劃分到不一樣的對象池中。例如,最經常使用的配置含有兩代,一個是年輕代,一個是老舊代。

在執行垃圾回收的時候,不一樣的代會使用不一樣的算法,不一樣的算法是基於不一樣代的特色進行優化的。一般的垃圾回收器會利用這些觀察結果,好比弱代假說,它被應用於許多編程語言編寫的應用程序,包含java編程語言:

大部分配給對象的內存,不會被長時間的引用(既不會存活好久),這意味着,這些對象 是英年早逝的。

不多存在從老一代到新一代的引用。

年輕代的垃圾回收會相對頻繁、高效、快速,這是由於年輕代的內存空間一般比較小而且被認爲其中的大部分對象不會存在一個長時間的引用。

一些年輕代中的對象會存活下來,最終促進收集器將他們放入到老一代當中。見圖一,老一代的回收器比年輕代的大而且增加相對緩慢得多,所以,老一帶的回收行爲比較難以發生,可是一旦發生了,就會耗費比較長的時間來完成。

 

爲年輕代選擇的垃圾回收器算法一般是速度優先的,由於年輕代的回收速度很是頻繁。另外一方面,老一代的回收器一般使用更節省內存的算法,由於老一代佔據了大部分的堆而且老一代算法必須在垃圾密度低狀況下的工做得很好。

Java SE 5.0 HotSpot JVM 的垃圾回收器

java HotSport 虛擬機包含了4種垃圾回收器,好比java SE 5.0 update 6.這些垃圾回收器都是基於代垃圾回收器的。在這一章節中,咱們將討論不一樣類型的代回收器,而且討論爲何對象分配一般是快速和高效的。下面會提供每一種收集器的詳細信息。

HotSpot的代劃分

Java HotSpot 虛擬機中的內存,被分配到三個代中:一個年輕代,一個老舊代,一個永久代。大部分的對象將被初始化並分配在年輕代中,老舊代中包含的對象來自於部分存活的年輕代,還有一些大的對象也會被直接分配到老一代中。永久代持有的對象是方便於垃圾回收器管理的對象,好比類和方法的描述,以及本身的類和方法。

年輕代包含一個被稱之爲Eden的空間和兩個較小的倖存空間,如圖2,大部分的對象都被初始化分配在Eden區域中(正如前面所說,一些比較大的對象可能會直接分配在老一代),倖存空間至少保持一個年輕代收集器運行時候,倖存下來的對象,這些對象被賦予更多的死亡機會以前,被認爲「足夠老」,則會被晉升到老一代。在任何給定的時間彙總,生存空間中的一個保持着全部「倖存」的對象,而另外一個則是空置的,直到下一次回收器開始工做。

 

垃圾回收器的類型

當年輕代的空間被填滿的時候,年輕代的回收器(有時被稱爲次要的回收器)就會在此時開始工做。當老一代或者永久代的空間被填滿的時候,全部的垃圾回收器就會開始工做,這就是說,全部代上的回收器都會開始工做。,通常狀況下,年輕代的垃圾回收器會先開始工做,

使用的收集算法是專門爲年輕代進行設計的算法,由於年輕代須要一個最高效的算法來識別年輕代中的垃圾對象。而後,老一代和永久代上的垃圾回收器將根據爲他們設計的算法開始進行工做。若是選擇了壓縮空間,那麼每一代上的空間都將被壓縮。

有的時候老一代的空間太滿以致於不能接受來自於年輕代中轉移過來的全部對象,特別是年輕代的回收器是優先工做的時候。在這種狀況下,除開CMS以外的全部回收器,在年輕代上的回收算法都被中止執行,做爲替代,老一代的回收器算法一般會在整個堆中執行(老一代上的CMS回收器是一個特殊的回收器,由於他沒法在年輕代上展開回收工做)。

快速分配

正如你所看到的以下所述的垃圾回收器,在多數狀況下,內存上存在着大量連續的內存塊來分配給對象。使用一個簡單的凹凸指針技術在這些塊上分配對象是高效的,這就是說始終保持跟蹤在以前分配的對象後面。當一個新的分配請求須要執行的時候,全部須要作的事情就是檢查代上剩餘的內存是否知足須要分配的對象的要求,若是知足,則更新指針的位置,而且初始化對象。

對於多線程的應用程序,分配操做必須是線程安全的。若是使用一個全局鎖來保證這一件事,那麼在代上分配空間將成爲一個瓶頸,而且下降性能。爲了解決這個問題,HotSpot JVM 使用一個稱之爲 線程本地分配緩存(Thread-Local- Allocation Buffer)的技術,它給每個線程提供一個獨有的緩存(一小部分的代空間),來提高多線程分配的吞吐量。因爲每個TLAB上只有一個線程進行分配操做,分配操做能夠利用凹凸指針,無需使用任何鎖定,使操做迅速到位。只有在少數狀況下,當一個線程的的TLAB填滿和須要一個新的TLAB的時候,必須使用同步來予以確認。因爲使用了TLAB技術,使得(JVM)最大限度地減小了內存浪費。例如,由選擇器分配大小的TLAB,平均浪費的空間小於Eden的1%。結合使用TALB和 基於凹凸指針的線性分配技術,使整個分配生效,僅僅只須要大約10個本地指令。

串行回收

使用串行回收器,在「暫停世界」策略中,年輕代和老舊代之間的操做是串行完成的(使用一個單一的CPU)。這意味着,回收器工做的時候,應用程序應當中止執行。

使用串行回收器的年輕代

圖3展現了一個使用串行回收器的年輕代的工做過程。Eden中存活的對象被複制到一個剛初始化並且空着的倖存空間(圖中的To 標籤),除非那些太大的對象而沒法放到To空間中,這樣的對象直接複製到老一代的空間之中。那些已經在倖存空間中佔用空間的還活着的對象(from 標籤)還相對年輕的話將被拷貝到另外一個倖存空間中,當這些活着的對象相對老的話則拷貝到老一代的空間中。注:若是To空間已經變滿,那些來自Eden和From的活對象將不會被拷貝到To中而是放入年老區中,無論他們在多少次的年輕代回收操做中倖存下來。任何剩餘在Eden 或者 From 空間的對象在活着的對象被拷貝以後,根據定義他們都是「死」對象,咱們不須要去檢查他們。(在圖中垃圾回收器會給他們打上叉標記,雖然在事實上回收器不會去檢查和標記這些對象)。

 

在年輕代的垃圾回收器結束以後,Eden 和 From空間已經清空,僅僅是To空間中包含活着的對象,在這一點上,倖存空間充當着一個交換空間的角色。

 

老舊代上的串行垃圾回收器

老舊代和永久代上的串行垃圾回收器使用一種叫 標記-清理-壓縮(mark-sweep-compact)的算法。在標記階段,收集器識別哪些對象是存活的。清理階段,回收器將掃描整個代上的空間,識別出垃圾對象。隨後,回收器執行滑動壓縮,移動或者的對象前進到老舊代空間的前面部分(永久代也是如此),在空間的尾部留下一塊連續的空間塊。如圖5,壓縮操做使得老舊代和永久代上,使用凹凸指針就能快速地完成分配操做。

 

何時使用串行回收

串行回收器是大多數運行在客戶端的機器上的應用程序的選擇,並且他們沒有低暫停時間的要求。在今天的硬件中,串行回收器能夠很是高效地管理大多數擁有64MB堆空間和全收集最壞狀況下的暫停時間少於半秒 的重要應用程序。

串行回收器的選擇

在j2SE5.0版本中,串行回收器會被沒有指名「server-class」的機器自動選擇做爲默認的垃圾回收器,這將會在第五章節中說到。在其餘的機器上,串行回收器贊成經過使用 -XX:+UseSerialGC命令行選項來明確使用。

並行回收器

在今天,許多java 應用程序運行在擁有足夠多的內存和多核心的cpu機器上,並行回收器,也叫吞吐回收器,被設計用來發揮多個cpu的特色來承擔單個CPU中垃圾回收器的工做。

使用並行回收器的年輕代

年輕代上的並行回收器使用的並行算法借鑑了串行回收器。它仍然是一個使用了「中止世界」和複製的回收器,可是年輕代上的並行回收器在執行過程當中使用到了多個cpu,下降了垃圾回收器的時間上限,而且增長了應用程序的吞吐量。圖6展現了年輕代上串行回收器和並行回收器之間的不一樣之處。

 

使用並行回收器的老舊代

老舊代上的並行垃圾回收器使用和串行回收器一致的 標記-清理-壓縮(mark-sweep-compact) 算法。

何時採用並行選擇器

並行回收器適合那些運行在擁有多個CPU的機器上且對暫停時間沒有限制的應用程序,雖然這種狀況不多,可是有可能會很長,老一代的回收器將仍然工做。那些合適使用並行回收器的應用程序的例子有,批處理,記帳,工資單,科學計算等等。

你應當考慮選擇並行壓縮回收器(在下面描述)來替代並行回收器,由於 form空間執行的併發回收操做是在多個代空間之間的,不只僅是在年輕空間。

並行空間的選擇

在J2SE 5.0 發行版,在server-class機器上並行回收器是默認的垃圾回收器,在其餘的機器中,你能夠經過執行 -XX:+UseParallelGC 命令行選項來明確打開並行選擇器

並行壓縮選擇器

並行壓縮選擇器是J2SE 5.0 update 6中引入的,它和並行選擇器的區別是它在老舊代上的垃圾回收上採用了一個新的算法。注:最後,並行壓縮選擇器終將要替代並行選擇器。

使用並行壓縮算法的年輕代

年輕代上的並行壓縮算法採用和年輕代上的並行算法是同樣的。

使用並行壓縮算法的老舊代

使用並行壓縮算法的老舊代和永久代在回收時使用 「中止世界」,多數併發出如今滑動壓縮上。選擇器採起三個階段。首先,每一代的空間在邏輯上劃分爲固定大小的區域。在標記階段,存活對象的初始集合 應用程序代碼中,可達的存活對象,在垃圾回收器的線程之間,而後全部的存活對象會在這個階段中標記出來。當一個對象被標記爲存活,它的所在的區域的數據將會被更新,更新的內容是這個對象的大小和位置。

總結階段的操做是基於區域,而不是對象。因爲在上一次的壓縮中,每一代的左側區域一般是密集的,包含着大部分存活的對象。一些能夠被清空的密集區域上進行壓縮操做是不值得的。因此總結階段要作的第一件事是檢查區域中的密度,從最左邊的一個開始,直到它到達一個點,這個點所在的空間能夠從其所在的區域中清空和那些空間中的右側是值得壓縮的區域。在這個點的左側區域都打上密集的前綴,而且不會移動這些區域上的任何一個對象。這個點的右側區域都將被壓縮,消除全部的死亡空間。總結階段統計而且存儲每個區域的第一個存活對象的第一個字節的新位置。注:總結階段當前被實現爲串行階段;並行是可行的,可是對於標記和壓縮階段並行不是性能的重要部分。

在壓縮階段,垃圾回收器線程使用總結階段的數據來肯定被填充的區域,而後線程能夠獨立地拷貝數據到區域中。這會形成堆的前面是密集的對象,後面是空閒的塊。

何時使用並行壓縮回收器

在不止一個cpu的機器上運行的應用程序適合使用並行壓縮回收器。在老舊區域上的並行操做下降暫定時間而且使得有暫停時間限制的應用程序上並行壓縮的選擇器比並行選擇器更合適。並行壓縮選擇器不適合那些運行在大型共享機器上的應用程序,由於在那些機器上面沒有任何一個單獨的應用程序能夠獨佔好幾個CPU,這個會延長時間的週期。在這樣的機器上,要麼減小垃圾回收器使用的線程個數(使用–XX:ParallelGCThreads=n這個選項)要麼選擇別的回收器。

如何選擇並行壓縮回收器

若是你要使用並行壓縮回收器,你必須明確地調用 -XX:+UseParallelOldGC 這個命令行選項。

併發 標記-清除(CMS)回收器

對於一些端對端的應用程序來講,吞吐量並以是一個快速響應的一個重要因素。年輕代回收器一般不會形成一個長時間的暫停。然而老舊代回收器在少數狀況下可能會致使一個長時間的暫停,特別是涉及到一個很大的堆的時候。爲了解決這個問題,HotSpot JVM 包含了一個稱之爲 併發標記-清除(CMS)的回收器,也叫低延遲迴收器。

使用CMS的年輕代回收器

CMS回收器在年輕代上的操做和併發回收器所作的同樣。

使用CMS回收器的老舊代

應用程序上老舊代的大多數回收操做使用CMS回收器來併發完成。

CMS回收器的回收週期以一個稱之爲初始化標記的短暫暫停開始,它肯定在程序代碼中可達的存活對象的集合。接着,在整個併發標記階段回收器將根據這個集合,標記出全部或者的的對象。由於在併發標記階段期間,應用程序會運行和更新引用,所以在併發標記階段沒法保證全部的存活對象都會被標記。爲了處理這個問題,應用程序會再次暫停,稱之爲再標記,此時將訪問和標記那些在併發標記階段被更新的的對象。由於再標記的暫停比初始標記更可靠,這回提高多線程會併發執行的效率。

再標記階段的末尾,堆中全部的存活對象能夠保證都被標記了,因而隨後的併發清除階段將會回收全部被定義的垃圾。圖7展現了老舊代上使用串行標記-清除和並行標記-清除回收器的不一樣點。

 

由於一些任務,好比再標記階段中再次訪問對象,提高了回收器要完成的工做數量,這一樣會提高了上限。這是一個大多數的回收器試圖下降暫停時間的典型權衡。

CMS回收器是惟一一個不採用壓縮的回收器。這意味着,在清空死亡對象佔用的空間以後,它不會移動存活對象到老舊代的一端。如圖8

 

這樣會節約時間,可是由於空閒空間不是連續的,回收器將再也不能使用一個簡單的指針來標明下一個待分配對象可使用的空閒位置。爲了解決它,咱們如今要使用一個空閒空間的鏈表。也就是說,他須要建立一些指向內存中還沒有分配區域的鏈表,每當須要爲一個對象分配空間的時候,一個適當的鏈表(基於內存所需的數量)必須搜索一個足夠大的區域來持有這個對象,正因如此,在老舊區域上分配空間是一個相對簡單凹凸指針技術更昂貴的操做。這一樣會額外提高年輕代上回收操做的上限,當來自年輕代上的對象提高到老舊代上的時候,這樣的分配會大量地出現。

另外一個CMS回收器的缺點是,它須要一個比別的回收器更大的堆需求。考慮到應用程序會在標記階段繼續運行,它將繼續分配內存,這會致使老舊代的持續增加。雖然收集器保證在標記階段定義出全部的存活對象,在這個階段一些對象會成爲垃圾而且不能被再次利用直到下一次回收器工做,這些對象被成爲 漂浮垃圾。

缺乏壓縮過程,最終仍是會出現碎片。爲了處理碎片,CMS回收器會指定一個經常使用的對象大小,估算出將來的需求,而且會分割或者合併空閒的內存塊去符合需求。

與其餘的回收器不一樣,CMS回收器不會在老舊代的空間變滿的時候開始回收工做。它試圖在足夠早的時間就開始回收工做,所以他能夠在老舊代變滿以前就完成回收工做。另外,CMS回收器減小了串行和並行壓縮器使用的「暫停世界」,標記-清除-壓縮算法的時間成本。爲了不它,CMS回收器基於 統計以前回收操做的時間和老舊代開始被佔用的時間。CMS回收器一樣會在這個時候啓動回收操做,當老舊代被佔用的時間超過了一個稱之爲初始化佔用時間的值。初始化佔用時間能夠經過–XX:CMSInitiatingOccupancyFraction=n 這個命令行選項來設置,n是老舊代對象大小的百分比,默認值是68.

簡單總結下,和並行回收器相比,CMS回收器下降了老舊代的暫停時間,這有些戲劇性,由於它提高了年輕代的暫停時間,減小了部分吞吐量,須要額外的堆大小。

增量模式

在這個模式中CMS回收器能夠在併發階段更快速地完成。這個模式打算減小由長期併發階段形成的影響,它採用了按期地暫停當前的併發階段使得當前的工做被掛起,讓出處理器來處理應用程序。它的工做是這樣的,在年輕代回收操做工做之間,回收器將會根據預期分配到不一樣的塊中。當應用程序須要經過併發回收來獲取一個短期暫停而又運行在小數量處理器的機器中時,這是頗有用的。更多關於使用這個模型的信息,參見第九章節「java5.0 虛擬機上的垃圾回收器調整」。

何時使用CMS回收器

若是你的應用程序須要一個短時暫停的垃圾回收器,而且可讓垃圾回收器在應用程序運行過程當中分享處理器資源,那麼就合適使用CMS回收器(因爲它的併發性,CMS回收器在回收週期使用的cpu週期與應用程序無關)。通常狀況下,應用程序擁有一個相對大的集合來存儲長期存活的對象(一個足夠大的老舊代),而且運行它的機器擁有兩個或更多的處理器,經常趨向於使用這個回收器。好比web服務器,CMS垃圾回收器一般被那些須要短時暫停的需求的應用程序所採用。它一般也適合那些在單個處理器上擁有合適老舊代大小的交互式應用程序。

使用CMS 選擇器

若是你打算是使用CMS選擇器,你必須明確地使用-XX:+UseConcMarkSweepGC這個命令行選項,若是你打算讓他運行在增加模式中,一樣須要使用 -XX:+CMSIncrementalMode 選項

5自動調整-自動選擇和行文調整

在J2SE 5.0發行版中,默認的垃圾回收器,堆大小和 Hotspot 虛擬機(無論是客戶端,仍是服務器)都是根據應用程序所運行的平臺和操做系統進行自動選擇的。當不設置任何命令行選項的時候,自動選擇的結果會更適合各類類型的應用程序。

另外,並行垃圾回收器添加了一個新的動態調整回收。依靠這個功能,只要用戶指按期望的行爲,垃圾回收器器動態調整每個區域的堆大小,來符合行爲的需求。這種組合了依靠平臺來默認選擇和依靠預期行爲垃圾回收器調整的東西被叫作人工智能。人工智能的目的就是爲最小化命令行調整的JVM提供一個優異的性能。

回收器,堆大小和虛擬機的的自動選擇

一個服務器類的機器將根據如下之一的條件進行定義

2個或2個以上物理處理器

2個或2個以上千兆字節的物理內存

這個服務器類機器的定義適用於全部平臺,除開window操做系統上運行的32位平臺。

在非服務器類的機器上,默認的JVM選項,垃圾回收器,和堆大小以下

客戶端JVM

串行垃圾回收器

初始堆大小爲4MB

最大堆大小爲64MB

在服務器類服務器,JVM將永遠使用服務器JVM直到你顯示指定-client 命令行選項來使用客戶端JVM,在一個服務器類機器上運行服務器JVM,默認的垃圾回收器是並行回收器,不然默認的回收器是串行回收器。

在一個服務器類機器上使用並行垃圾回收器來運行這些JVM(客戶端JVM或者服務器JVM),堆的默認初始值和最大值以下

初始堆大小是物理內存的1/64 ,最可能是1GB。(注意:;最小的初始堆大小是32MB,由於一個服務器類的機器定義的是最少含有2GB的內存,而且2GB的 1/64 是32MB)

堆最大是物理內存的 1/4,最多1GB。

另外,這些默認值一樣適用於 非服務器類機器(初始化4MB堆大小和最大64MB堆大小)。默認值能夠經過命令行選項來重定義,相關選項在第8章給出。

調節並行回收器的默認行爲

在j2SE5.0的發行版本中,添加了一個調節並行回收器的新方法,基於應用程序對於垃圾回收器的指望行文,使用命令行選項來指定這個指望的行爲來達成最大暫定時間和應用程序吞吐量的目標。

最大暫停時間目標

最大暫停時間目標的指定使用下面的命令行選項

-XX:MaxGCPauseMillis=n

這個解釋將對並行回收器的暫定時間進行提示,最大暫停時間是n秒或者少於n。並行回收器將調整堆大小和其餘的垃圾回收相關的選項來試圖保持垃圾回收器的暫停時間小於n秒。這個調整會致使垃圾回收器下降應用程序的全局吞吐量,在一些狀況下預期的暫停時間目標可能沒法被實現。

最大暫停時間的目標策略分別被運用在每個代上。通常地,若是目標沒有被達成,代會變得更小來試圖達成目標。默認最大暫停時間沒有被設置。

吞吐目標

最大吞吐目標意思是依據花費在垃圾回收和花費在非垃圾回收器(代指應用程序時間)上的時間。這個目標能夠經過如下命令行選項來明確指定

- XX:GCTimeRatio=n

垃圾回收器和應用程序時間的比值是

1 / (1 + n)

例如:-XX:GCTimeRatio=19 設置的目標是垃圾回收器佔用全部時間的5%,默認的目標是1%(n=99)。這個花費在垃圾回收器上的時間,是指全部代上垃圾回收器的時間總和。若是目標的吞吐量沒有被完成,代空間將會增加使得在 兩次垃圾回收工做期間,應用程序的運行時間得以增加。,一個巨大的代空間須要更多的時間來填滿。

足跡目標

當吞吐量和最大暫停時間的目標被完成,垃圾回收器會下降堆的大小直到目標沒有被完成。目標的完成邊界就是足跡的到達點。

目標的順序

並行回收器以完成最小暫停時間爲第一目標,只有在這個目標完成以後纔會去追求吞吐量的目標,足跡目標只有在前兩個目標被完成後纔開始考慮。

6建議

建議這一章補充了以前的章節介紹了自動配置垃圾回收器,虛擬機和堆大小的選擇,覆蓋了絕大部分的應用程序的合理配置。所以,初始的垃圾回收器的選擇和配置什麼都沒作。這就是說,不須要特別指定垃圾回收器,注:讓系統基於平臺和操做系統爲你的應用程序進行自動選擇。而後測試你的程序。若是它的性能是能夠接受的,擁有足夠高的吞吐量和足夠低的暫停時間。你就不須要修改垃圾回收器的選項。

另外一方面,若是你的應用程序試圖經過垃圾回收來獲取高性能,那麼你最優先作的事情就是思考根據你的應用程序和你的平臺所提供的默認垃圾回收選擇器是不是合適的。若是不合適,那麼你要明確地選擇一個你認爲合適的選擇器,而且查看你的平臺是否兼容。

你可使用章節七給出的工具來測量和分析性能。基於工具給出的結果,你能夠考慮修改選項,例如控制堆大小或者垃圾回收器的行爲。章節八中將給出一些通用的指定選項。請注意:最合適的性能調節,應當是先測量,而後再調節。測量的測試你實際上使用的代碼相關。另外,請勿過分優化,由於應用程序的數據集,硬件等等,甚至垃圾回收器的實現都會隨着時間的改變而改變。

這一章節提供了關於選擇垃圾回收器和指定堆大小的信息。而後提供了調節並行垃圾回收器的選項,和提供了一些關於處理 OutOfMemoryErrors的建議。

什麼時候選擇一個不一樣的垃圾回收器

在章節四中,介紹個每一個回收器的適用情形。章節五描述了不一樣的平臺上串行回收器和並行回收器的默認選擇。若是你的應用程序或者環境特性與默認的回收器的適用狀況不一樣,請使用如下的其中一個命令行選項來明確使用一個垃圾回收器。

–XX:+UseSerialGC

–XX:+UseParallelGC

–XX:+UseParallelOldGC

–XX:+UseConcMarkSweepGC

堆大小

章節五描述了默認的初始和最大堆大小。這些值符合大多數的應用程序,可是若是你的性能分析出現了問題(見章節七)或者出現了OutOfMemoryError(將會在下面進行描述)顯示你的問題出如今某一個代或者全部的堆的大小上,你能夠經過章節八中提供的命令行選項來修改你的大小。例如,默認的最大堆大小是64MB這個在非服務器類機器上一般過小了,於是你能夠經過 -Xmx 來指定一個更大的值。除非你的問題與長的暫停時間相關,否則你能夠試圖使用全部可使用的內存。吞吐量與可使用的內存成正比。擁有足夠的可利用內存是影響垃圾回收器性能的重要因素。

在明確了你可以爲全部的堆提供多少的內存以後,你能夠開始考慮調整不一樣的代上的大小了。以下描述,讓回收器自動和動態修改堆大小來完成這個行爲。

調整並行回收器的策略

若是垃圾回收器的選擇(自動或者明確使用)是並行回收器或者並行壓縮回收器,那麼開始而且指定一個知足應用程序的吞吐量目標(見章節五)。不要選擇一個最大的堆大小除非你明白你須要一個比默認最大堆大小還大的堆。堆會自動增加或者收縮它的大小來支持選擇的吞吐量目標。在初始化期間和改變應用程序的行爲來符合指望的期間堆大小會有一個擺動。

若是堆大小增加到它的最大值,在絕大多數的狀況下這意味着在當前的最大堆大小下吞吐量目標沒法被實現。爲應用程序設置最大的堆大小值來接近平臺上全部的物理內存但不包含引交換分區。再一次運行這個應用程序。若是吞吐量目標仍然沒有被實現,那麼應用程序的目標運行時間對於該平臺上的可用的內存來講過高了。

若是吞吐量目標能夠被實現,可是暫停的時間太長了,選擇一個最大的暫停時間。選擇一個最大的暫停時間意味着你的吞吐量目標將不會被實現,所以應當選擇一個應用程序能夠妥協的值。

堆大小會在垃圾回收器試圖知足相互競爭的目標之間進行搖擺,即便應用程序達到一個穩定的狀態。這之間的壓力來自達到吞吐量目標(這將會須要一個更大的堆)與最大暫停時間和最小足跡(這將會須要一個更小的堆)。

如何處理OutOfMemoryError

一個許多開發者常常遇見的問題是,應用程序由於java.lang.OutOfMemoryError而終止。這個錯誤在沒有足夠的內存來爲對象進行分配的時候拋出來。這就是說,垃圾回收器不能找到更多的空間來容納這個新的對象,而且堆沒法進一步擴大。一個OutOfMemoryError 並不能直接確認是內存泄露的問題。這個問題也許是一個配置問題,例如指定的堆大小(若是不指定的話則是默認的)不能知足應用程序的需求。

診斷OutOfMemoryError的第一步是檢查全部的錯誤信息。在異常信息中,進一步的信息會在「java.lang.OutOfMemoryError」以後提供。這裏有一些常見的例子包含了這些額外信息的內容、意義和處理方式。

Java heap space(java 堆空間)

這代表了一個對象沒法在堆上分配。這個問題可能只是一個配置問題。你能夠捕獲到這個錯誤,例如,若是使用 -Xmx命令行選項來指明最大的堆大小(或者是默認值)沒法知足應用程序的需求。他也可能代表一個再也不被使用的對象不被垃圾回收器回收,由於應用程序無心地保持了這些對象的引用。HAT工具(見章節七)能夠用來觀察全部的可達對象和明確哪個引用來保持哪個對象的存活。另外一個潛在的錯誤來源有多是在應用程序中過多地使用了 finalizers 以至於線程調用 finalizers 沒法跟得上添加finalizers到隊列的速度。Jconsole管理工具能夠用來監控在銷燬期間的對象的數目。

PermGen space(永久代空間)

這代表了永久代的空間已經滿了。如以前的描述,這是JVM存儲元數據的堆區域。若是一個應用程序加載一個很大數量的類,那麼永久代會自動增加。你能夠指明永久代的大小經過命令行選項 XX:MaxPermSize=n ,n是指明的大小。

Requested array size exceeds VM limit(請求的數組大小超過虛擬機的限制)

這代表了應用程序試圖分配一個數組空間大於了堆的大小。例如,若是一個應用程序試圖分配一個512MB的數組,可是最大的堆大小隻有256MB,那麼這個錯誤將會被拋出。在大多數狀況下,這個問題緣由多是堆大小過小或者是一個BUG致使了應用程序試圖建立一個被計算錯誤的巨大數組。

在章節七中介紹的一些工具能夠用來診斷 OutOfMemoryError 問題。在這些有用的工具中有一些是 堆分析工具(HAT Heap Analysis Tool),好比jconsole管理工具和使用-histo選項的jmap工具。

7評估垃圾回收器性能的工具

各類診斷和監測工具能夠用來評估垃圾收集的性能,本章節提供了一些簡要的概述來描述其中的一些工具,更多的信息請參見章節九中的「工具和故障排除」鏈接。

–XX:+PrintGCDetails 命令行選項

一個簡單的用來獲取回收器的初始信息的方法是指定 –XX:+PrintGCDetails 命令行選項。對於任意一個回收器,這個的輸出信息包含了每個代在垃圾回收以前和以後存活的對象大小,每個代上的全部可用空間,回收器消耗的時間。

–XX:+PrintGCTimeStamps 命令行選項

這會輸出每個回收器的啓動時間戳,另外若是你使用–XX:+PrintGCDetails 命令行選項這些信息也會輸出。這些時間戳會幫助你將回收器的日誌和別的事件日誌關聯起來。

jmap

jmap是一個命令行工具,包含在Solaris操做系統環境和linux(不包含windows)的Java 開發工具集(JDK)中。它會打印出運行中的JVM或者核心文件的內存相關統計數據。在不使用任何命令行選項的狀況下,它會打印出全部被加載的共享對象,與Solaris的pmap工具類似的輸出。對於更多的明確信息,可使用 -heap,-histo,或 -permstat 選項。

-heap 選項用來獲取一些信息包含了垃圾回收器的名字,具體的算法細節(例如 並行垃圾回收器使用的線程數量),堆的配置信息,和堆的簡單使用狀況。

-histo 選項能夠用來獲取堆上的類的直方圖,對於每個類,它會打印出堆中該類的實例數量,這些對象所佔用的單位爲字節的內存總數,和全合格的類名。當你試圖理解堆的佔用狀況的時候,這個直方圖會頗有用。

配置永久代的大小對於應用程序來講是很重要的,特別是動態加載一個很大數據量的類的時候(好比 java Server Pages(JSP)和web containers(web 容器))。若是一個應用程序加載了過多的類,那麼將會拋出OutOfMemoryErrorJmap的 -permastat 選項能夠用來獲取永久代上的對象統計信息。

Jstat

Jstat工具在Hotspot JVM使用了一個構件時儀表盤來提供應用程序運行中消耗的資源和性能信息。當診斷性能問題的時候可使用這個工具,而且一些特殊的和堆大小以及垃圾回收器相關的問題也可使用它。他的其餘選項能夠輸出有關垃圾回收器行爲和各類代的容量和使用狀況的統計信息。

HPROF:堆分析(Heap Profiler)

HPROF 是一個包含在JDK 5.0的簡單分析代理。他是一個使用java虛擬機工具接口(Java Virtual Machine Tools Interface )的動態鏈接庫接口。它會輸出分析信息到一個文件或者套接字中,以ASCII 或者二進制的格式。這些工具能夠進一步地使用一個前端分析工具來處理。

HPROF可以展現CPU的使用率,堆分配統計,和監控競爭配置。此外,它能夠輸出完整的堆垃圾和報告java虛擬機上全部的監控器和線程的狀態。HPROF在分析新能,鎖競爭,內存泄露和其餘問題的時候頗有用。參見章節九HPROF文檔的鏈接。

HAT:堆分析工具(Heap Analysis Tool)

堆分析工具(HAT)用來幫助調試無心的對象保留。這個術語用來描述一個再也不被須要的對象因爲被一個存活的對象所引用而保持存活。HAT提供了一個方便的手段來瀏覽對象在堆中的快照。這個工具容許必定數量的查詢,包含「向我提供全部從根集合到對象的引用路徑」,參見章節九的HAT文檔連接。

8垃圾收集相關關鍵選項

咱們可使用一些命令行參數選項來選擇垃圾回收器,指定堆或者代上的大小,調整垃圾回收器的行爲,和活的垃圾回收器的統計信息。這一章節展現了其中的最普遍使用的選項。對於關於多方面的有效選項的更多完整列表和詳細信息,見章節九。注:指定的數量時以「m」或者「M」結尾,表明兆字節,「k」或者「K」結尾表明千字節,「g」或者G結尾表明千兆字節。

垃圾回收器的選擇

選項

選擇的垃圾回收器

–XX:+UseSerialGC

Serial 串行

–XX:+UseParallelGC

Parallel 並行

–XX:+UseParallelOldGC

Parallel compacting 並行壓縮

–XX:+UseConcMarkSweepGC

Concurrent mark–sweep (CMS) 併發標記清除

垃圾回收器的統計

選項

描述

–XX:+PrintGC

打印每個垃圾回收器的基礎信息

–XX:+PrintGCDetails

打印每個垃圾回收器更詳細的信息

–XX:+PrintGCTimeStamps

打印每個垃圾回收器開始事件的時間戳。使用–XX:+PrintGC–XX:+PrintGCDetails 能夠在每一次垃圾回收器開始的時候打印這些內容。

堆和代的大小

選項

默認值

描述

–Xmsn

See Section 5

堆的初始大小,單位爲字節,

–Xmxn

See Section 5

堆的最大大小,單位爲字節

–XX:MinHeapFreeRatio=minimum

and

–XX:MaxHeapFreeRatio=maximum

40 (min)

70 (max)

空閒空間佔總空間比例的目標。這會運用於任何一代上。例如,若是最小值是30,而且空閒空間佔該代上的空間比例小於30%,那麼這個代空間就會擴展直到知足30%的空閒空間。近似的,若是最大值是60而且自由空間的比例已經超過60%,代空間的大小就會收縮直到自由空間只佔到60%。

–XX:NewSize=n

平臺相關

默認的年輕代上的初始大小,單位爲字節

–XX:NewRatio=n

2 on client JVM,

8 on server JVM

年輕代和老舊代的比例。例如,若是n=3,那麼比例就是1:3而且Eden空間和倖存(survivor)空間一共佔年輕代和老舊代的總空間的 1/4。

–XX:SurvivorRatio=n

32

倖存空間和 Eden空間的比例,例如,若是n=7,每個倖存空間就是年輕代的 1/9(不是 1/8由於倖存空間有兩個)

–XX:MaxPermSize=n

平臺相關

永久代的最大大小

並行和並行壓縮回收器的選項

選項

默認值

描述

–XX:ParallelGCThreads=n

CPU的個數

垃圾回收器的線程數

–XX:MaxGCPauseMillis=n

沒有默認值

指示回收器的暫停時間爲n秒或者更少。

–XX:GCTimeRatio=n

99

設置目標花費在垃圾回收器上的時間佔總時間的 1/(1+n)

CMS回收器的選項

選項

默認值

描述

–XX:+CMSIncrementalMode

不啓用

開啓併發階段的增加模式,按期地暫停當前的併發階段,掛起它的工做,並讓出處理器來爲應用程序工做

–XX:+CMSIncrementalPacing

不啓用

容許基於應用程序的行爲,在放棄處理器以前自動控制CMS回收器的工做量

–XX:ParallelGCThreads=n

CPU的個數

年輕代上的並行回收器的線程個數,和老舊代上的並行部分的線程個數

9更多的信息

HotSpot 垃圾回收器和性能調整

Java HotSpot 虛擬機的垃圾回收器

(http://www.devx.com/Java/Article/21977)

java 5.0虛擬機上調整垃圾回收器

(http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html)

自動調整

服務器類機器的斷定

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/server–class.html)

垃圾回收器的自動調整

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/gc–ergonomics.html)

Java 5.0 虛擬機的自動調整

(http://java.sun.com/docs/hotspot/gc5.0/ergo5.html)

選項

Java HotSpot 虛擬機的選項

(http://java.sun.com/docs/hotspot/VMOptions.html)

Solaris 和 linux 選項

(http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/java.html)

Windows選項

(http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/java.html)

工具和故障排除

JavaSE 5.0平臺的問題查找和診斷入門

(http://java.sun.com/j2se/1.5/pdf/jdk50_ts_guide.pdf)

HPROF:一個javaSE 5.0的 堆和CPU 分析工具

(http://java.sun.com/developer/technicalArticles/Programming/HPROF.html)

Hat:堆分析工具

(https://hat.dev.java.net/)

內存釋放

內存釋放,線程,和java基礎內存模型

(http://devresource.hp.com/drc/resources/jmemmodel/index.jsp)

如何處理java內存釋放的內存保留問題

(http://www.devx.com/Java/Article/30192)

其餘

J2SE 5.0 的發行日志

(http://java.sun.com/j2se/1.5.0/relnotes.html)

Java虛擬機

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/index.html)

Sun java實時系統(java RTS)

(http://java.sun.com/j2se/realtime/index.jsp)

垃圾回收器的一些書籍:《垃圾回收器:自動動態內存管理算法》做者 Richard Jones Rafael Lins John Wiley 和 Sons1996年

相關文章
相關標籤/搜索