java Hotspot 內存管理白皮書

jopen 11年前發布 | 42K 次閱讀 Java開發 Java

1引言

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

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

第二章節中,我們為讀者展示最新的自動內存管理器的概念,在那里我們將討論手動為數據分配內存空間對于程序員的好處。

第 三章節中,我們將簡述通常的垃圾回收器的概念,設計的選擇,以及性能指標。本章節中,我們還引入了稱之為“代”的,一個常用的內存組織方式,它將基于對象 的預期壽命來將不同的對象劃分到不同的區域中。這種按照代來劃分的方式已經被證明了能夠有效地減少垃圾回收器的暫停時間和讓每一個應用擁有一個良好的性價 比。

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

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

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

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

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

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

2手動VS自動內存管理

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

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

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

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

垃圾回收器避免了引用懸空的問題,因為一個被引用的對象將永遠不會被垃圾回收器收回,所以他所占用的內存就不會被視為空閑區。垃圾回收器也解決了內存泄漏的問題,因為它會自動釋放所有不再被引用的對象所占用的內存。

3垃圾回收器的概念

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

分配內存

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

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

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

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

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

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

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

理想的垃圾收集器的特點

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

并且垃圾不應超過一定收集周期之后仍然沒有被收集。同時也意味著垃圾回收器應該是高效的,不應當使用應用程序的暫停來完成他的垃圾回收工作。

然而,在大多數實時操作系統中,時間,空間和頻率往往是相互取舍的,舉個例子,如果堆空間過小,收集工作會更快,但是堆也會被迅速地填滿,因此需要更頻繁的收集操作,相反地,一個足夠大的堆空間,需要更長的時間來填滿,因此收集頻率會更低,但是收集工作會耗費更多的時間。

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

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

設計選擇

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

串行或者并行:

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

并發或者停掉整個世界:

當 通過“停止世界”的方式來運行垃圾回收器,垃圾回收器會在收集過程中暫停應用程序。另外,一個或者多個垃圾回收器任務可以并發執行,就是說可以與應用程序 一起同時運行。通常情況下,一個并發的垃圾回收器可以并發執行大多數的工作,然是同時也可能偶爾需要做短時的“停止世界”的暫停操作。“停止世界”的垃圾 回收器是簡單的并發回收器,因為堆在收集過程中是被凍結且對象不能改變的,但是他的缺點是,我們可能不希望某一些應用程序在運行過程中被暫停。相應的,暫 停時間較短的并發進行垃圾收集器,但是收集器必須格外小心,因為在同一時間,應用程序更新操作的對象和垃圾收集器操作的對象可能正在同時進行。這樣會增加 并發回收器的運行開銷 ,這會影響到收集器的性能和需要更大的堆。

壓縮 VS 不壓縮 VS 拷貝:

在 垃圾回收器已經決定哪些內存中的對象是活著的,哪些是垃圾之后,他就可以開始壓縮內存了,移動所有存活的對象到一起(移動到一個連續的空間中),并且完全 回收其余的內存。壓縮之后,我們可以非常容易和快速地將一個新的對象分配在第一個空閑的內存位置上,并且利用一個簡單的指針來跟蹤對象的下一個位置來作為 下一次分配的起始地點。與壓縮收集器相反,非壓縮收集器釋放垃圾對象所占用的內存空間之后并不會像壓縮收集器一樣移動所有的存活對象來釋放一塊連續的空閑 區域。這樣做的好處是可以更快地完成垃圾收集,但是缺點是會造成潛在的碎片。通常來說,從內存就地釋放的堆中分配內存比已經壓縮過的堆中分配內存更昂貴。 因為它必須從堆中搜索出一塊連續并且足夠大的內存來容納新的對象。

第三種方法就是拷貝回收器,他復制或者調整活著的對象到另一個內存區域中,它的好處就是對象所在的舊內存區域可以被認為是空閑的內存區域,可以非常快速和容易地進行后續的分配,但缺點就是拷貝舊內存區域中的對象到新的區域中需要額外的時間和空間。

性能指標:

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

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

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

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

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

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

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

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

代回收

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

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

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

很少存在從老一代到新一代的引用。

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

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

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

為年輕代選擇的垃圾回收器算法通常是速度優先的,因為年輕代的回收速度非常頻繁。另一方面,老一代的回收器通常使用更節省內存的算法,因為老一代占據了大部分的堆并且老一代算法必須在垃圾密度低情況下的工作得很好。

Java SE 5.0 HotSpot JVM 的垃圾回收器

java HotSport 虛 擬機包含了4種垃圾回收器,比如java SE 5.0 update 6.這些垃圾回收器都是基于代垃圾回收器的。在這一章節中,我們將討論不同類型的 代回收器,并且討論為什么對象分配通常是快速和高效的。下面會提供每一種收集器的詳細信息。

HotSpot的代劃分

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

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

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

垃圾回收器的類型

當年輕代的空間被填滿的時候,年輕代的回收器(有時被稱為次要的回收器)就會在此時開始工作。當老一代或者永久代的空間被填滿的時候,所有的垃圾回收器就會開始工作,這就是說,所有代上的回收器都會開始工作。,一般情況下,年輕代的垃圾回收器會先開始工作,

使用的收集算法是專門為年輕代進行設計的算法,因為年輕代需要一個最高效的算法來識別年輕代中的垃圾對象。然后,老一代和永久代上的垃圾回收器將根據為他們設計的算法開始進行工作。如果選擇了壓縮空間,那么每一代上的空間都將被壓縮。

有 的時候老一代的空間太滿以至于不能接受來自于年輕代中轉移過來的所有對象,特別是年輕代的回收器是優先工作的時候。在這種情況下,除開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 空間的對象在活著的對象被拷貝之后,根據定義他們都是“死”對象,我們不需要去檢 查他們。(在圖中垃圾回收器會給他們打上叉標記,雖然在事實上回收器不會去檢查和標記這些對象)。

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

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

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

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

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

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

什么時候使用串行回收

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

串行回收器的選擇

在j2SE5.0版本中,串行回收器會被沒有指名“server-class”的機器自動選擇作為默認的垃圾回收器,這將會在第五章節中說到。在其他的機器上,串行回收器同意通過使用 -XX:+UseSerialGC命令行選項來明確使用。

并行回收器

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

使用并行回收器的年輕代

年輕代上的并行回收器使用的并行算法借 鑒了串行回收器。它仍然是一個使用了“停止世界”和復制的回收器,但是年輕代上的并行回收器在執行過程中使用到了多個cpu,降低了垃圾回收器的時間上 限,并且增加了應用程序的吞吐量。圖6展示了年輕代上串行回收器和并行回收器之間的不同之處。

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

使用并行回收器的老舊代

老舊代上的并行垃圾回收器使用和串行回收器一致的 標記-清理-壓縮(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展示了老舊代上使用串行標記-清除和并行標記-清除回收器的不同點。

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

因為一些任務,比如再標記階段中再次訪問對象,提升了回收器要完成的工作數量,這同樣會提升了上限。這是一個大多數的回收器試圖降低暫停時間的典型權衡。

CMS回收器是唯一一個不采用壓縮的回收器。這意味著,在清空死亡對象占用的空間之后,它不會移動存活對象到老舊代的一端。如圖8

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

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

另一個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結尾代表千兆字節。

垃圾回收器的選擇

選項

</td>

選擇的垃圾回收器

</td> </tr>

–XX:+UseSerialGC

</td>

Serial 串行

</td> </tr>

–XX:+UseParallelGC

</td>

Parallel 并行

</td> </tr>

–XX:+UseParallelOldGC

</td>

Parallel compacting 并行壓縮

</td> </tr>

–XX:+UseConcMarkSweepGC

</td>

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

</td> </tr> </tbody> </table>

垃圾回收器的統計

  • 選項

    </td>

    描述

    </td> </tr>

    –XX:+PrintGC

    </td>

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

    </td> </tr>

    –XX:+PrintGCDetails

    </td>

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

    </td> </tr>

    –XX:+PrintGCTimeStamps

    </td>

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

    </td> </tr> </tbody> </table>

    堆和代的大小

    選項

    </td>

    默認值

    </td>

    描述

    </td> </tr>

    –Xmsn

    </td>

    See Section 5

    </td>

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

    </td> </tr>

    –Xmxn

    </td>

    See Section 5

    </td>

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

    </td> </tr>

    –XX:MinHeapFreeRatio=minimum

    and

    –XX:MaxHeapFreeRatio=maximum

    </td>

    40 (min)

    70 (max)

    </td>

    空 閑空間占總空間比例的目標。這會運用于任何一代上。例如,如果最小值是30,并且空閑空間占該代上的空間比例小于30%,那么這個代空間就會擴展直到滿足 30%的空閑空間。近似的,如果最大值是60并且自由空間的比例已經超過60%,代空間的大小就會收縮直到自由空間只占到60%。

    </td> </tr>

    –XX:NewSize=n

    </td>

    平臺相關

    </td>

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

    </td> </tr>

    –XX:NewRatio=n

    </td>

    2 on client JVM,

    8 on server JVM

    </td>

    年輕代和老舊代的比例。例如,如果n=3,那么比例就是1:3并且Eden空間和幸存(survivor)空間一共占年輕代和老舊代的總空間的 1/4。

    </td> </tr>

    –XX:SurvivorRatio=n

    </td>

    32

    </td>

    幸存空間和 Eden空間的比例,例如,如果n=7,每一個幸存空間就是年輕代的 1/9(不是 1/8因為幸存空間有兩個)

    </td> </tr>

    –XX:MaxPermSize=n

    </td>

    平臺相關

    </td>

    永久代的最大大小

    </td> </tr> </tbody> </table>

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

  • 選項

    </td>

    默認值

    </td>

    描述

    </td> </tr>

    –XX:ParallelGCThreads=n

    </td>

    CPU的個數

    </td>

    垃圾回收器的線程數

    </td> </tr>

    –XX:MaxGCPauseMillis=n

    </td>

    沒有默認值

    </td>

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

    </td> </tr>

    –XX:GCTimeRatio=n

    </td>

    99

    </td>

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

    </td> </tr> </tbody> </table>

    CMS回收器的選項

    選項

    </td>

    默認值

    </td>

    描述

    </td> </tr>

    –XX:+CMSIncrementalMode

    </td>

    不啟用

    </td>

    開啟并發階段的增長模式,定期地暫停當前的并發階段,掛起它的工作,并讓出處理器來為應用程序工作

    </td> </tr>

    –XX:+CMSIncrementalPacing

    </td>

    不啟用

    </td>

    允許基于應用程序的行為,在放棄處理器之前自動控制CMS回收器的工作量

    </td> </tr>

    –XX:ParallelGCThreads=n

    </td>

    CPU的個數

    </td>

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

    </td> </tr> </tbody> </table>

    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  和  Sons 1996年

     本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
  • sesese色