1. 程式人生 > >JVM執行時資料區-堆

JVM執行時資料區-堆

基本概念

    堆,jvm程序啟動時建立,為jvm程序的所有執行緒共享的區域,所有Java物件和陣列(jdk8+也包含字串常量)均在堆上分配記憶體,所以堆的大小決定了能存放多少Java物件,當堆滿或者無法容納新建立的物件時,則需要通過垃圾收集器進行垃圾回收,在回收過程中會造成應用停頓,影響應用效能。所以在JVM效能調優中,需要在堆的大小,應用需要建立的物件的多少和大小,可容忍應用停頓時間以及目標吞吐量之間找到一個平衡點。

堆的記憶體分佈

  • 堆根據物件的存活的時間,分為新生代Young和老年代Old。而新生代由進一步分為Eden區和兩個Survivor區。而進行這種分代的原因主要是優化GC效能,因為物件大部分生命週期都很短,如果不進行分代,則在進行GC時,則要掃描整個堆,導致應用長時間停頓;而採用分代後,則規定:新建立的物件都在新生代的Eden中分配記憶體(如果是大物件則直接在老年代分配,具體由引數-XX:PretenureSizeThreshold指定,大於),當Eden無法容納新物件時,則傳送一次MG。
  • 初始時,兩塊Survivor區都是空的,分別可叫做From S和To S,其中To始終是空的,用於存放在MG時,在Eden和From S中存活的物件,這樣做的目的主要是保證存活物件在連續的地址空間,避免產生記憶體碎片。第一次MG時,選擇其中一塊作為From S,將Eden中存活的物件移動過來;而在之後的MG中,則將Eden和From S中存活的物件移動到To S,然後清空Eden和From S,此時To變成From,From變成To。每進行一次MG,存活物件年齡加一,當達到MaxTenringThresHold的值(預設為15),則晉升移動到老年代。
  • 除了達到MaxTenringThresHold的大小的物件晉升到老年代這種固定情況外,還有兩種動態情況:
  1. Survivor中存活的物件中,相同年齡的物件的大小的和大於Survivor空間(為From S,如From,To均為1M,則大於512K)的一半,則Surviror中大於等於這個年齡的物件全部晉升到老年代;
  2. 在發生MG時,To無法存放Eden和From中移動過來的物件時,溢位物件直接晉升到老年代,此時在gc日誌可看到"premature promotion"(過早提升)。
  • 當從新生代晉升存活物件到老年代時,如果老年代沒有那麼多空間來容納這些物件,則老年代發生FGC來獲取空間。除了這種情況會觸發FGC外,還包括:
  1. 直接在老年代分配大物件,老年代容納不下時觸發FGC,FGC後如果還是存放不下,則會出現OOM:java heap space的錯誤;
  2. 統計得到MG晉升到老年代的物件平均大小大於老年代可用空間時,也會觸發FGC;
  3. 在CMS垃圾收集器中,出現promotion failed(從suvivor過來的物件太大,老年代存放不下)和concurrent mode failed(在老年代進行CMS時,MG有物件從suvivor晉升到老年代)時也會出現FGC;
  4. 除此之外還有Perm區空間慢,顯示呼叫System.gc()(如使用RMI進行RPC時,預設每一小時進行一次FGC,System.gc()可用通過引數DisableExplicitGC來禁止)
  • 注意在老年代垃圾收集器中,如CMS執行時,並不是FGC,CMS是跟使用者執行緒一起執行的,是在當老年代記憶體佔用達到某個比例時,CMS開始工作。而FGC是在老年代無法容納晉升的物件時觸發。而MG和FGC均會發生STW造成應用停頓,只是MG時間很短可忽略。具體分代情況如圖:

JVM引數解析

  • 堆大小相關:-Xms:jvm堆初始大小,預設實體記憶體的1/64,-Xmx:jvm堆最大分配大小,預設實體記憶體的1/4;動態調整預設為噹噹前已分配堆中,空閒少於40%則調大直到Xmx,空閒超過70%則調小直到Xms;為了效能考慮,通常將Xms於Xmx設定為一樣大,從而避免每次GC後調整堆的大小。
  • 新生代:堆=新生代+老年代,新生代大小可以通過:-XX:NewSize/MaxNewSize  》 -Xmn 》-XX:NewRatio,推薦使用-Xmn,相當一次設定了NewSize/MaxNewSize。NewRatio為old和young的比例,預設值為2,-server預設為2:1,如果young分得太少,則會造成大量物件直接進入old,影響效能。經驗效能優化採用3/8原則,即新生代和老年代的比值為3/5。
  • Eden與Survivor:-XX:SurvivorRatio,預設為8,即eden和survivor的比例為8:1,即from和to各佔新生代的1/10。一般保持預設值,對效能影響不大。
  • 堆大小調整與GC的關係:新生代和老年代:對新生代自身而言,增大新生代MG時間增加,頻率降低;減小則MG時間減少,頻率增大。對老生代而言,新生代越大,則老年代越小,增大FGC次數,降低每次FGC的時間。

最佳實踐

  • 檢視gc日誌每次MC,FGC後,整體堆大小,新生代大小,老年代大小,從而確定程式執行實際需要多大的堆,新生代多大,老年代多大。或者根據gc日誌分析,如使用網站:http://gceasy.io/index.jsp#banner,分析日誌,得出young,old分配大小,峰值大小等資料,從而設定合理的堆大小Xms,Xmx和Xmn。基於實際值後,調大適當的倍數再觀察調整,直到最優倍數。具體來說為確定一個物件活躍數,即程式穩定執行後長期存活在堆中物件所佔空間大小,這個可以通過FGC後檢視老年代物件的大小,或者更精確可以在程式穩定後,多次獲取GC資料來計算一個平均值,最終以這個活躍數為參考,適當增大以下倍數:

            

  • 伺服器記憶體和應用對吞吐量和響應時間的考慮:伺服器記憶體足夠,則增大新生代的大小;不夠,則適當增大Survivor的大小,而Survivor的具體大小,則需要根據MC之後,存活的物件的大小多次調整,計算公式:

       Survivor 空間計算公式: survivor 空間大小 = -Xmn[value] / (-XX:SurvivorRatio=<ratio> + 2)  

  • 吞吐量優先的應用,則儘可能調大新生代,垃圾回收使用並行;響應時間優先的應用,則儘可能設大新生代,直到接近應用最低響應時間限制,因為新生代越大,則傳送MC的頻率越大,同時減少到老年代的物件(此時老年代較小,可能FGC會增多)。