JVM-GC工做原理

  配置Garbage Collection  2012-09-17 14:53:18

分類: Javahtml

 

 

配置Garbage Collection - 伏虎 - 追夢人

        上面這幅圖是我從網絡上摘到的,它展示了在一個的理想系統的模型下GC對系統的影響。圖的最頂上紅色線條顯示出一個應用程序在單處理器環境下花費1%的時間作GC的狀況。而其轉換到32個處理器的設備上將致使損失超過20%的吞吐率。作10%的GC將損失超過70%的吞吐率。所以能夠看出對GC稍稍的調整能夠帶了性能巨大的變化。java

        1、概述算法

        Java 2 平臺的一個最大好處在於將開發者從大量錯綜複雜的內存分配和回收中解放出來。可是,一次GC也可能成爲一個主要的瓶頸問題,所以它變成有價值和必要去理解那些隱藏的實現部分。GC老是假設應用程序使用對象的策略,GC的這些行爲能夠經過可調的參數來進行調整以提升性能。緩存

        在運行的程序中再也不被任何一個指針所引用,則它就成爲垃圾。許多簡單的垃圾回收算法僅僅是簡單的迭代每個可到達的對象,一些殘留的不可到達的對象就成爲垃圾。這種方式會依據存活對象的數量而成比例的影響時間的消耗,當一個龐大的應用有許多存活的對象時,這個代價是巨大的。服務器

        從JVM1.3版開始已經加入一些不一樣的垃圾回收算法用於GC。當簡單的垃圾回收器檢查每個在堆中存活的對象時,垃圾回收器利用憑經驗獲取的許多應用程序的屬性設置去迴避額外的工做。網絡

        這些屬性中最重要的是infant mortality。分佈式

配置Garbage Collection - 伏虎 - 追夢人

         上圖中藍色區域是一個典型的對象生存時間的分佈。在圖中左邊描述的對象呈現山峯形狀,它們能夠在分配後馬上回收利用。迭代器對象,如單循環中是存活的。有些對象存活期很長,所以它一直延伸到圖的右邊。例如,一些典型的對象從初始化開始一直到程序退出都一直存活着。在兩端中間存活的對象都存在於一些中間計算過程當中,在圖中表如今infant mortality峯頂的右邊這麼一團藍色區域。一些應用程序的圖看上去會很是的不一樣,但它們會和此圖類似。有效收集可能被高度關注,大部分對象在Young中就死亡了。性能

         爲此,內存在代(Generations)中被管理:內存池控制着不一樣年齡的對象。當Generation被添滿時,GC在就被開始工做,每一個代都會獨自發生這種狀況。對象在Eden中被分配,由於初期不少對象都在這裏死亡。當Eden被填滿時就會發生minor collection,其中一些倖存的對象被移動到Old Generation中,當Old Generation須要被回收,這就是一個major collection ,它的運行經常很是慢,由於它要涉及全部存活着的類。測試

       許多收集器都會去容忍長時間倖存的對象,並減小GC的發生。默認的GC參數設置是被用於許多小應用程序的。它們不是去優化許多服務器應用的,記住:優化

       若是GC成爲瓶頸,你應該去配置Generation的大小;檢查GC的輸出,而且嘗試改變配置適合你獨特狀況的配置。

       2、收集類型

       每種不一樣的收集類型都對系統產生不一樣影響,從JDK 1.3版本以後系統提供了3類徹底不一樣三種GC:

       Copying:有時又被稱爲Scavenge,這個收集器在兩個或多個代中移動對象很是高效,原代被清空,容許殘留的死亡對象被快速回收,然而,Coping要求更多的軌跡,所以它將請求一個空閒內存去工做。在1.3.1版本後Copying收集被應用於全部的minor collections。

        Mark-compact:這個收集器容許代在不增長額外內存的狀況下在本地回收。可是很明顯它要慢於Copying方式。在1.3.1版本後mark-compact 收集被應用於全部的major collections。

        Incremental:有時又被稱爲train。此收集器只在當在命令行中指定-Xincgc 才生效。但通過仔細的簿記,Incremental GC在同一個時間僅僅回收old generation 中的一部分,並引發major collection 一個長時間暫停,甚至超過許多minor collections。可是考慮到所有性能,它一般比Mark-compact收集慢。

        所以copying收集是最快的,使用copying儘量收集許多對象是咱們的目標,而不是使用Mark-compact和Incremental收集。

        默認的代的排列以下圖所示:

配置Garbage Collection - 伏虎 - 追夢人

       在最開始的時候,最大的地址空間其實是被保留的,除非必要它才被分配。全部的地址空間被做爲對象保留,內存能夠被分配到一個Young和Old代中。

       一個Young代包含一個Eden和二個survivor空間,對象最開始是被分配在Eden中的,一個Survivor空間在任什麼時候間都是空的,服務於在Eden和另外一個Survivor空間中下一個Copying收集後仍然存活的對象。對象以這種方式在Survivor空間之間進行拷貝,直到它們的年齡達到持久化(此時被拷貝到Old代中)。

      (其餘虛擬機,包括1.2版JVM,爲Copying使用兩個大小相等的空間,而不是一個大的Eden和二個小的空間。這意味者Young代的大小不是直接能夠比較的。)

        Old代在這裏是採用mark-compact進行收集的。

        Permanent代的調用是很特殊的部分,它掌握着JVM本身映射的數據如類和對象方法。

        3、性能注意事項

       GC性能有兩個重要的方面:

       吞吐率:在GC中在長時間運行狀況下有效利用時間佔總時間的百分比。吞吐率包括了在分配中浪費的時間(但不包括分配速度的調整是不包括的)。

       暫停時間:當一個應用程序出現由於GC工做而中止工做的時間。

       用戶能夠有不一樣的關於GC的需求。例如,一些優先考慮Web 服務的吞吐率,所以能夠忍受暫停,或被網絡因素所掩蓋。單一個交互的圖像程序,即時短暫的暫停都是讓用戶沒法滿意的。

       一些用戶對其它的東西敏感。

      軌跡是一個進程的做業集,用於權衡在頁和緩存中的行數。一個在物理內存或多處理器方面受限的系統,軌跡能夠有不少伸縮性。

      敏捷度是從一個對象死亡到這個內存變成可用之間的時間跨度。一個重要的考慮就是分佈式系統的RMI。

      通常的,代的大小選擇就是基於這些考慮進行的。例如,一個很是巨大的Young代能夠提供最大的吞吐率,但要付出軌跡和敏捷度的代價。使用一個小的Young帶,採用incremental收集方式暫停時間能夠最小化,但要付出吞吐率的代價。

       這裏不存在惟一正確的方法來配置代的大小,最好的方法是應用程序使用的內存和用戶需求一致。由於這個理由JVM是沒有選擇優化的,用戶能夠經過命令行的方式對其進行修改。

       4、測量方法

      對於一個應用程序吞吐率和軌跡能夠經過指標來測量,例如,Web服務的吞吐率能夠經過在客戶端裝載發生器來進行測試,軌跡在Solaris操做系統中經過pmap命令來獲取。暫停時間能夠經過GC的輸出預估出來。

   經過在命令行中加入-verbose:gc 能夠打印出每個收集器的信息,例如,下面的例子給出了一個大型服務應用的輸出:

  [GC 325407K->83000K(776768K), 0.2300771 secs]
  [GC 325816K->83372K(776768K), 0.2454258 secs]
  [Full GC 267628K->83769K(776768K), 1.8479984 secs]

  經過這個輸出咱們能夠看到有二個minor collections工做和一個major collections工做。經過箭頭先後數據咱們能夠看到GC前和後活着的對象佔用內存大小。minor collections 後包括對象的數量不表示他們還活着,只是表示他們沒有被回收。這是由於他們可能確實活着,也可能他們在Old代中。圓括號中間的數字表示能夠內存大小,它是總堆大小減去一個survivor空間大小。

   5、代的大小

  下圖中給出了一些能夠改變代大小的參數。

配置Garbage Collection - 伏虎 - 追夢人

        6、所有堆

       當代被填滿後發生收集,吞吐率和可用內存數量成反比例,總的可用內存大小很是影響GC的性能。每次收集時無論是增減,JVM都會努力按照必定的比率爲存活的對象保留自由空間。這個目標比例是一個用百分數表示的參數-XX:MinHeapFreeRatio=<minimum> 和 -XX:MaxHeapFreeRatio=<maximum> 的集合,而且總的堆的大小界定於-Xms 和-Xmx 指定的範圍內,大小值大於-Xms 指定的值,但小於-Xmx 指定的值。

      在Solaris操做系統中默認的值以下:

      -XX:MinFreeHeapRatio= 40

   -XX:MaxHeapFreeRatio= 70

   -Xms                = 3584k

   -Xmx                = 64m
   大型的服務器應用系統應用這個默認配置有兩個方面的問題,一是啓動很慢,由於初始堆很是小必須使用屢次major collections來調整堆的大小。一個更緊迫的問題是默認的最大堆的大小對於大多少服務應用來講過度的小,對於服務應用程序的經驗法則是:

   除非暫停時間對你是不容忽視的問題,不然分配可能的足夠大的內存給JVM,默認的64M內存過小。設置-Xms 和-Xmx 到相同的值,以增長JVM作重要的大小調整時被進行內存清理的可能性。另外一方面,若是你的配置不恰當,JVM是不能對此進行補償和優化的。

   必定要增長內存就如同你增長處理器同樣,由於內存分配是能夠並行的,但GC是不能並行工做的。

   7、Young代

   第二個有影響的突出問題是堆的一部分被劃歸Young代專用。 一個更大的Young代常不多發生minor collections。在一個有限的堆中,較大的Young代意味着有很小的Old代,這將增長major collections發生的頻率。優化的選擇來源於應用中生命週期的分佈。

   默認的Young代的大小由NewRatio參數控制,例如,設置-XX:NewRatio=3意味着Young代和Old代的比率是1:3。換句話說,Eden和Survivor空間總和是整個堆大小的四分之一。

   NewSize和MaxNewSize兩個參數指定了Young代的下限和上限值。設定他們相等的值就固定了Young代大小,就如同設置-Xms和-Xmx 兩個值相等就固定整個堆大小同樣。這種方式比經過NewRatio參數調整總體倍數要處理的更加精細。

   由於Young代採用的是copying收集,在Old代中必須有充足的空閒內存被保留這樣才能保障minor collection 能夠正常完成。最壞的狀況,這個被保留的內存大小會等於Eden空間大小加上Survivor中非空空間大小之和。在最糟糕的狀況下,若是Old代中沒有足夠可用的空間,一個major collection 將被代替發生。這個策略對於小應用系統是很是好的,由於在Old代中保留的內存其實是被提交但沒有被使用。除非應用程序須要一個最大的堆,Eden空間超過堆中實際提交內存大小的一半是沒有意義的,此時major collections將發生。

   若是指望SurvivorRatio參數能夠用來調整Survivor空間大小,但它經常不是最重要的。例如,-XX:SurvivorRatio=6設置Survivor和Eden空間的比例爲1:6,換句話朔,每個Survivor空間將是Young代大小的八分之一(爲何不是七分之一?由於Young帶中有二個Survivor空間)。

   若是Survivor空間過小,Copying收集將直接將其轉移到Old代中。若是Survivor空間太大,他們將無有的空閒。每個JVM的GC都會選擇一個對象生存時間的閥值,低於這個閥值的將作Copying收集,高於這個閥值的將被轉入Tenured。這個門檻值被設置爲Survivor空間一半滿。(在1.3.1版本後可使用-XX:+PrintTenuringDistribution 來顯示這個門檻值和對象在新代中的年齡,這對於觀察應用程序生命週期的分佈是有益的)

   下面列出了在Solaris操做系統中的默認值:

   NewRatio       =  2   (client JVM: 8)

   NewSize        =  2172k

   MaxNewSize     =  32m

   SurvivorRatio  =  25

   服務應用系統的一些遵循的準則以下:

   首先肯定能夠給予JVM的總的內存值,描繪你本身的性能指標,而後依靠Young代的大小去發現最好的設置。

   除非你發現有過多的major collection 或 暫停時間過長,你應該分配大量的內存到Young代,默認的MaxNewSize 爲32MB,這過小了。

   增長Young代大小到總堆大小的一半或少些將是無效果的。

   增長Young代大小就如同你增長處理器數量同樣,由於內存分配是並行的,但GC不能並行。

   8、其它考慮
   許多應用程序的permanent代大小設置對於GC來講是不合適的。一些應用程序動態的產生和裝載不少類,例如,一些JSP的實現就是這麼作的。若是必要的話,經過MaxPermSize參數能夠設置最大的permanent代的大小。

   一些使用finalization和weak/soft/phantom 引用的應用程序的GC是相互影響的。這些因素能夠致使在JAVA程序語言級別的進行優化的問題,一個依賴finalization關閉文件描述的例子,這就產生一個外部資源依賴GC的敏捷度來完成工做,利用GC來管理資源這是一個十分糟糕的想法。

   另外一種應用程序經過明顯的調用GC來實現和GC的互動,例如經過System.gc() 來調用。這些調用強制執行major collection,在大系統上抑制了其可伸縮性。顯示調用GC會受到-XX:+DisableExplicitGC標誌的影響。

   通常顯示使用GC的地方發生在RMI的分佈式GC時,應用程序使用RMI涉及到對象在另外一個JVM中,除偶然本地分配的對象外,這些分佈式應用的垃圾是不能被回收的,如有RMI週期性的強制進行徹底的收集。這些強制收集的頻率是能夠被一些屬性控制的,例如:

   java -Dsun.rmi.dgc.client.gcInterval=3600000
       -Dsun.rmi.dgc.server.gcInterval=3600000 ...

   這代表原來默認每分鐘進行一次的頻率被替換成每小時進行一次。它可能由於其它對象的緣由花費更長的時間才被收集。這些屬性能夠被設置成很是高很是長,MAX_VALUE 能夠設置時間介於無限大時間之間,若是沒有特別說明這個值能夠是DGC(分佈式GC)時間線的上面。

      在服務器上的JVM對軟件引用的清除比在客戶機上較積極。經過增長-XX:SoftRefLRUPolicyMSPerMB=10000這樣一個參數能夠將清除率減慢,默認值是1000,或每兆字節一秒。

   對於巨大系統他們還有其它參數能夠去改善性能。

   9、結論

   GC在高度並行的系統裏會成爲瓶頸。理解GC是如何工做的,再經過命令行參數去下降影響。

相關文章
相關標籤/搜索