簡述

GC 即垃圾收集機制是指 JVM 用于釋放那些不再使用的對象所占用的內存。

java 語言并不要求 JVM GC ,也沒有規定 GC 如何工作。

不過常用的 JVM 都有 GC ,而且大多數 GC 都使用類似的算法管理內存和執行收集操作。

?

在充分理解了垃圾收集算法和執行過程后,才能有效的優化它的性能。

有些垃圾收集專用于特殊的應用程序。

比如,實時應用程序主要是為了避免垃圾收集中斷,而大多數 OLTP 應用程序則注重整體效率。

理解了應用程序的工作負荷和 JVM 支持的垃圾收集算法,便可以進行優化配置垃圾收集器。

?

垃圾收集的目的在于清除不再使用的對象。

GC 通過確定對象是否被活動對象引用來確定是否收集該對象。

GC 首先要判斷該對象什么時候可以收集。

兩種常用的方法是引用計數和對象引用遍歷。

引用計數存儲對特定對象的所有引用數,也就是說,當應用程序創建引用以及引用超出范圍時, JVM 必須適當增減引用數。

當某對象的引用數為 0 時,便可以進行垃圾收集。

?

早期的 JVM 使用引用計數,現在大多數 JVM 采用對象引用遍歷。

對象引用遍歷從一組對象開始,沿著整個對象圖上的每條鏈接,遞歸確定可到達( reachable )的對象。

如果某對象不能從這些根對象的一個(至少一個)到達,則將它作為垃圾收集。

在對象遍歷階段, GC 必須記住哪些對象可以到達,以便刪除不可到達的對象,這稱為標記( marking )對象。

?

下一步, GC 要刪除不可到達的對象。

刪除時,有些 GC 只是簡單的掃描堆棧,刪除未標記的對象,并釋放它們的內存以生成新的對象,這叫做清除( sweeping )。

這種方法的問題在于內存會分成好多小段,而它們不足以用于新的對象,但是組合起來卻很大。

因此,許多 GC 可以重新組織內存中的對象,并進行壓縮( compact ),形成可利用的空間。

?

為此, GC 需要停止其他的活動。

這種方法意味著所有與應用程序相關的工作停止,只有 GC 運行。

結果,在響應期間增減了許多混雜請求。

另外,更復雜的 GC 不斷增加或同時運行以減少或者清除應用程序的中斷。

有的 GC 使用單線程完成這項工作,有的則采用多線程以增加效率。

?

下面列舉一些 JVM 使用的 GC

標記-清除收集器:這種收集器首先遍歷對象圖并標記可到達的對象,然后掃描堆棧以尋找未標記對象并釋放它們的內存。這種收集器一般使用單線程工作并停止其他操作。

標記-壓縮收集器:有時也叫標記-清除-壓縮收集器,與標記-清除收集器有相同的標記階段。在第二階段,則把標記對象復制到堆棧的新域中以便壓縮堆棧。這種收集器也停止其他操作。

復制收集器:將堆棧分為兩個域,常稱為半空間。每次僅使用一半的空間, JVM 生成的新對象則放在另一半空間中。 GC 運行時,它把可到達對象復制到另一半空間,從而壓縮了堆棧。這種方法適用于短生存期的對象,持續復制長生存期的對象則導致效率降低。

增量收集器:把堆棧分為多個域,每次僅從一個域收集垃圾。這會造成較小的應用程序中斷。有多種方法可以定義實際的 GC

分代收集器 把堆棧分為兩個或多個域,用以存放不同壽命的對象。 JVM 生成的新對象一般放在其中的某個域中。過一段時間,繼續存在的對象將獲得使用期并轉入更長壽命的域中。分代收集器對不同的域使用不同的算法以優化性能。

并發收集器 并發收集器與應用程序同時運行。這些收集器在某點上一般都不得不停止其他操作以完成特定的任務,但是因為其他應用程序可進行其他的后臺操作,所以中斷其他處理的實際時間大大降低。

并行收集器 并行收集器使用某種傳統的算法并使用多線程并行的執行它們的工作。在多 cpu 機器上使用多線程技術可以顯著的提高 java 應用程序的可擴展性。

?

Sun Hotspot 1.4.1 JVM 堆大小的調整

Sun Hotspot 1.4.1 使用分代收集器,它把堆分為三個主要的域:新域、舊域以及永久域。 JVM 生成的所有新對象放在新域中。一旦對象經歷了一定數量的垃圾收集循環后,便獲得使用期并進入舊域。在永久域中 JVM 則存儲 class method 對象。就配置而言,永久域是一個獨立域并且不認為是堆的一部分。下面介紹如何控制這些域的大小。

可使用 -Xms -Xmx 控制整個堆的原始大小或最大值。比如,下面的命令是把初始大小設置為 128M

?java –Xms128m –Xmx256m

為控制新域的大小,可使用 -XX:NewRatio 設置新域在堆中所占的比例。比如下面的命令把整個堆設置成 128m ,新域比率設置成 3 ,即新域與舊域比例為 1 3 ,新域為堆的 1/4 32M

java –Xms128m –Xmx128m –XX:NewRatio =3

可使用 -XX:NewSize -XX:MaxNewsize 設置新域的初始值和最大值。比如,下面的命令把新域的初始值和最大值設置成 64m:

?java –Xms256m –Xmx256m –Xmn64m

一般不把永久域當作堆的一部分。永久域默認大小為 4m 。運行程序時, JVM 會調整永久域的大小以滿足需要。每次調整時, JVM 會對堆進行一次完全的垃圾收集。使用 -XX:MaxPerSize 標志來增加永久域搭大小。在 WebLogic Server 應用程序加載較多類時,經常需要增加永久域的最大值。當 JVM 加載類時,永久域中的對象急劇增加,從而使 JVM 不斷調整永久域大小。為了避免調整,可使用 -XX:Per m Size 標志設置初始值。比如,下面把永久域初始值設置成 32m ,最大值設置成 64m

java –Xms512m –Xmx512m –Xmn128m –XX:PermSize=32m –XX:MaxPermSize=64m

默認狀態下, HotSpot 在新域中使用復制收集器。該域一般分為三個部分。第一部分為 Eden ,用于生成新的對象。另兩部分稱為救助空間,當 Eden 充滿時,收集器停止應用程序,把所有可到達對象復制到當前的 from 救助空間,一旦當前的 from 救助空間充滿,收集器則把可到達對象復制到當前的 to 救助空間。 From to 救助空間互換角色。維持活動的對象將在救助空間不斷復制,直到它們獲得使用期并轉入舊域。

使用 -XX:SurvivorRatio 可控制新域子空間的大小。同 NewRation 一樣, SurvivorRation 規定某救助域與 Eden 空間的比值。比如,以下命令把新域設置成 64m Eden 32m ,每個救助域各占 16m

java –Xms256m –Xmx256m –Xmn64m –XX:SurvivorRation=2

如前所述,默認狀態下 HotSpot 對新域使用復制收集器,對舊域使用標記-清除-壓縮收集器。在新域中使用復制收集器有很多意義,因為應用程序生成的大部分對象是短壽命的。理想狀態下,所有過渡對象在移出 Eden 空間時將被收集。如果能夠這樣的話,并且移出 Eden 空間的對象是長壽命的,那么理論上可以立即把它們移進舊域,避免在救助空間反復復制。
但是,應用程序不能適合這種理想狀態,因為它們有一小部分中長壽命的對象。最好是保持這些中長壽命的對象并放在新域中,因為復制小部分的對象總比壓縮舊域廉價。

為控制新域中對象的復制,可用 -XX:TargetSurvivorRatio 控制救助空間的比例。該值是一個百分比,默認值是 50 。當較大的堆棧使用較低的 sruvivorratio 時,應增加該值到 80 90 ,以更好利用救助空間。

-XX:maxtenuring threshold 可控制上限。為放置所有的復制全部發生以及希望對象從 eden 擴展到舊域,可以把 MaxTenuring Threshold 設置成 0 。設置完成后,實際上就不再使用救助空間了,因此應把 SurvivorRatio 設成最大值以最大化 Eden 空間,設置如下:

java … -XX:MaxTenuringThreshold=0 –XX:SurvivorRatio 5000

?

JVM 中獲取信息以助于調整方案

-verbose.gc 開關可顯示 GC 的操作內容。打開它,可以顯示最忙和最空閑收集行為發生的時間、收集前后的內存大小、收集需要的時間等。

打開 -xx:+ printgcdetails 開關,可以詳細了解 GC 中的變化。

打開 -XX: + PrintGCTimeStamps 開關,可以了解這些垃圾收集發生的時間,自 JVM 啟動以后以秒計量。

最后,通過 -xx: + PrintHeapAtGC 開關了解堆的更詳細的信息。

為了了解新域的情況,可以通過 -XX:=PrintTenuringDistribution 開關了解獲得使用期的對象權。

?

BEA JRockit JVM 的使用

Bea WebLogic 8.1 使用的新的 JVM 用于 Intel 平臺。在 Bea 安裝完畢的目錄下可以看到有一個類似于 jrockit81sp1_141_03 的文件夾。這就是 Bea JVM 所在目錄。

不同于 HotSpot Java 字節碼編譯成本地碼,它預先編譯成類。 JRockit 還提供了更細致的功能用以觀察 JVM 的運行狀態,主要是獨立的 GUI 控制臺或者 WebLogic Server 控制臺。 Bea JRockit JVM 支持 4 種垃圾收集器:

分代復制收集器:它與默認的分代收集器工作策略類似。對象在新域中分配,即 JRockit 文檔中的 nursery 。這種收集器最適合單 CPU 機上小型堆操作。

單空間并發收集器:該收集器使用完整堆,并與背景線程共同工作。盡管這種收集器可以消除中斷,但是收集器需花費較長的時間尋找死對象,而且處理應用程序時收集器經常運行。如果處理器不能應付應用程序產生的垃圾,它會中斷應用程序并關閉收集。

分代并發收集器:這種收集器在護理域使用排它復制收集器,在舊域中則使用并發收集器。由于它比單空間共同發生收集器中斷頻繁,因此它需要較少的內存,應用程序的運行效率也較高,注意,過小的護理域可以導致大量的臨時對象被擴展到舊域中。這會造成收集器超負荷運作,甚至采用排它性工作方式完成收集。

并行收集器:該收集器也停止其他進程的工作,但使用多線程以加速收集進程。盡管它比其他的收集器易于引起長時間的中斷,但一般能更好的利用內存,程序效率也較高。


默認狀態下, JRockit 使用分代并發收集器。要改變收集器,可使用 -Xgc:<gc_name> ,對應四個收集器分別為 gencopy singlecon gencon 以及 parallel 。可使用 -Xms -Xmx 設置堆的初始大小和最大值。要設置護理域,則使用 -Xns:

java –jrockit –Xms512m –Xmx512m –Xgc:gencon –Xns128m

盡管 JRockit 支持 -verbose:gc 開關,但它輸出的信息會因收集器的不同而異。

JRockit 還支持 memory load codegen 的輸出。