一次JVM調優的筆記

1. JVM Tuning基礎知識

1.1 Java堆結構

  Java堆能夠處於物理上不連續的內存空間上,只要邏輯上是連續的便可。Java堆就是各類對象分配和保存的內存空間,線程間共享。Java堆分爲Eden區,Survivor區,tenured區和Permanent區,以下圖所示。java

  Java堆的分配原則以下:算法

  • Java堆分佈以下圖所示,新的類的實例大部分在Eden(之因此用Eden這個詞也就是表示初創起始的意思)區分配。
  • Eden區滿的時候,或者須要GC時,依然存活的對象將被複制到Survivor區,一共有兩個survivor區,當一個survivor區滿時,依然存活的對象將被複制到另外一個survivor區。
  • 當survivor區全滿時,從第一個survivor區複製過來的,且此時還存活的對象將被複制到Tenured Generation(年老代)
  • Perm Generation用於存放靜態文件,持久代大小能夠經過MaxPermSize進行設置。

 

  Perm Generation是JVM的駐留內存,用於存放JDK自身攜帶的CLASS,Interface的元數據等。被裝載進此區域的數據是不會被垃圾回收器GC回收掉的,關閉JVM時,釋放此區域所控制的內存。windows

  Java對象的聲明週期在堆中從Young到Tenured,這個期間從Eden誕生到Tenured死亡。當EDEN區不夠用時,將觸發minor gc,GC會對EDEN區進行垃圾回收,將再也不使用的對象進行銷燬,同時若是發現對象還被其餘對象引用,則將對象移動到survivor區,後面依此類推,當Tenured區發生GC時,稱爲major gc,因爲java中大部分對象的生命週期都很短,因此GC通常發生在Eden區,所以minor gc發生的頻率比major gc要高不少。若是最後整個堆空間都滿了,則會爆出異常JVM對空間溢出:java.lang.OutOfMemoryError: java heap space。服務器

1.2 JVM GC算法枚舉

  • Mark-sweep算法

        即標記回收算法,將須要回收的對象標記,再統一回收。這種算法適合Perm代的對象,覺得Perm代的存儲空間比較大,須要回收的又很少。多線程

  • Copying算法

     即複製算法,直接拷貝。適合Eden區的對象向survivor區拷貝,由於Eden區的對象大部分都須要GC,並且Eden區的容量小。併發

  •  Mark-compact算法

   即標記整理算法,與標記清除算法不一樣,標記整理算法避免了內存碎片問題,它將全部存活的對象向內存的一端移動,「整理」完成後,一端是存活的對象一端是可回收內存,而後直接清除可回收內存。eclipse

1.3 GC收集器

  • Serial收集器

   最基本、最古老的收集器。這是一個單線程收集器,GC過程當中,應用會被停掉,即stop-the-world。性能

  • Parnew收集器

   實現代碼與serial差很少,不一樣的是這是一個多線程回收器。測試

  • Paralled scavenge收集器 

     Paralled scavenger收集器是一個新生代回收器,使用複製算法,也是一個多線程並行收集器。parallel scavenge收集器專一於提升吞吐量,吞吐量定義爲:優化

            吞吐量 = 用戶程序執行時間/(用戶程序執行時間 + 垃圾回收時間)

  • Serial old收集器

   Serial old是Serial收集器的老年代版本。

  • Parallel old收集器

   Parallel Scavenge收集器的老年代版本。使用標記-整理算法。

  • CMS(concurrent mark sweep)收集器:併發標記清除收集器,以最短停頓時間爲目標的收集器。採用標記—清除算法實現。

 

2.VisualVM1實際監控JVM狀態

2.1 經常使用調優參數列表

  主要須要熟悉的是總存儲空間大小,各個區的比例設置,針對不一樣的區設定不一樣的回收器和回收算法。在默認狀況下有些選項是不打開的,所以須要手動配置,而且根據本身應用的狀況選擇適當的參數。

參數 描述

–XX:+UseSerialGC

使用串行GC收集器

–XX:+UseParallelGC

 使用並行GC收集器

–XX:+UseParallelOldGC

使用parallel old收集器 

–XX:+UseConcMarkSweepGC
使用CMS收集器 
 -Xms  初始狀態堆大小
 -Xmx 堆的最大大小
 -XX:MaxPermSize=n  設置Permanent區的大小
 -XX:NewSize=n  設置Young generation大小
-XX:NewRatio=n  設置年輕代和年老代的比值

 

 2.2 查看並分析GC日誌

    對於應用程序,在eclipse中能夠經過工程屬性增長VM屬性,將GC輸出保存爲日誌文件。

  

  GC輸出選項: 

  -XX:+PrintGC 輸出GC日誌
  -XX:+PrintGCDetails 輸出GC的詳細日誌
  -XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式)
  -XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
  -XX:+PrintHeapAtGC 在進行GC的先後打印出堆的信息
  -Xloggc:../logs/gc.log 日誌文件的輸出路徑

  日誌輸出形式:

Java HotSpot(TM) Client VM (25.45-b02) for windows-x86 JRE (1.8.0_45-b14), built on Apr 10 2015 10:46:40 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 2074576k(402044k free), swap 4149152k(1441572k free)
CommandLine flags: -XX:InitialHeapSize=16777216 -XX:MaxHeapSize=268435456 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:-UseLargePagesIndividualAllocation 
Heap
 def new generation   total 4928K, used 885K [0x04600000, 0x04b50000, 0x09b50000)
  eden space 4416K,  20% used [0x04600000, 0x046dd5f0, 0x04a50000)
  from space 512K,   0% used [0x04a50000, 0x04a50000, 0x04ad0000)
  to   space 512K,   0% used [0x04ad0000, 0x04ad0000, 0x04b50000)
 tenured generation   total 10944K, used 0K [0x09b50000, 0x0a600000, 0x14600000)
   the space 10944K,   0% used [0x09b50000, 0x09b50000, 0x09b50200, 0x0a600000)
 Metaspace       used 98K, capacity 2242K, committed 2368K, reserved 4480K

 

  編寫了一個簡單的遞歸計算Fibbonaccy數列的程序,並在最後增長了一句System.gc()強制進行Full GC,獲得的GC日誌以下,回收後Eden區使用量爲0。當服務器型的應用運行起來時,免不了會有屢次GC,經過GC日誌能夠比較容易定位性能問題,好比full gc次數過多等。

{Heap before GC invocations=0 (full 0):
 def new generation   total 4928K, used 1522K [0x04600000, 0x04b50000, 0x09b50000)
  eden space 4416K,  34% used [0x04600000, 0x0477c9e8, 0x04a50000)
  from space 512K,   0% used [0x04a50000, 0x04a50000, 0x04ad0000)
  to   space 512K,   0% used [0x04ad0000, 0x04ad0000, 0x04b50000)
 tenured generation   total 10944K, used 0K [0x09b50000, 0x0a600000, 0x14600000)
   the space 10944K,   0% used [0x09b50000, 0x09b50000, 0x09b50200, 0x0a600000)
 Metaspace       used 259K, capacity 2280K, committed 2368K, reserved 4480K
15.463: [Full GC (System.gc()) 15.477: [Tenured: 0K->776K(10944K), 0.0428098 secs] 1522K->776K(15872K), [Metaspace: 259K->259K(4480K)], 0.0688257 secs] [Times: user=0.02 sys=0.00, real=0.07 secs] 
Heap after GC invocations=1 (full 1):
 def new generation   total 4992K, used 0K [0x04600000, 0x04b60000, 0x09b50000)
  eden space 4480K,   0% used [0x04600000, 0x04600000, 0x04a60000)
  from space 512K,   0% used [0x04a60000, 0x04a60000, 0x04ae0000)
  to   space 512K,   0% used [0x04ae0000, 0x04ae0000, 0x04b60000)
 tenured generation   total 10944K, used 776K [0x09b50000, 0x0a600000, 0x14600000)
   the space 10944K,   7% used [0x09b50000, 0x09c122f0, 0x09c12400, 0x0a600000)
 Metaspace       used 259K, capacity 2280K, committed 2368K, reserved 4480K
}
Heap
 def new generation   total 4992K, used 45K [0x04600000, 0x04b60000, 0x09b50000)
  eden space 4480K,   1% used [0x04600000, 0x0460b4a8, 0x04a60000)
  from space 512K,   0% used [0x04a60000, 0x04a60000, 0x04ae0000)
  to   space 512K,   0% used [0x04ae0000, 0x04ae0000, 0x04b60000)
 tenured generation   total 10944K, used 776K [0x09b50000, 0x0a600000, 0x14600000)
   the space 10944K,   7% used [0x09b50000, 0x09c122f0, 0x09c12400, 0x0a600000)
 Metaspace       used 259K, capacity 2280K, committed 2368K, reserved 4480K

 

測試環境:

  JVM: Java HotSpot(TM) Client VM (25.60-b23, mixed mode, sharing)
  Java: version 1.8.0_60, vendor Oracle Corporation

Step1:

  監控界面,可以直觀地看到JVM各個區域的內存使用狀況。除了監視虛擬機,還能夠監視應用的內存使用狀況。嘗試對Eclipse的啓動進行優化,能夠很明顯的看到eclipse啓動時,S0的對象向S1拷貝,而old區的使用量略微增長。

  

  還能夠監控當前CPU和內存負載狀況,類加載數量和線程數:

  

  

Step2:

  eclipse安裝目錄下有個.ini文件,即eclipse的配置文件,將須要的參數配置填寫進去便可。啓動eclipse後,查看GC日誌,發現整個啓動過程當中共發生了27次GC,其中有3次Full GC,整個啓動過程,我掐指一算大概6~7秒,至關慢了。

  經過參數-XX:NewSize=n 將Eden區重設爲128m後,再次啓動eclipse發現,此次就只發生了11次GC,降低了通常還多,啓動速度也明顯比原來快了一些。

Step3:  

  接着使用-Xverify:none禁掉類加載時的字節碼驗證過程,GC次數爲10次,如今啓動速度基本爲3秒多左右,比原來快了一倍。

 

{Heap before GC invocations=10 (full 4):
 def new generation   total 36864K, used 35041K [0x04400000, 0x06bf0000, 0x0eea0000)
  eden space 32832K, 100% used [0x04400000, 0x06410000, 0x06410000)
  from space 4032K,  54% used [0x06410000, 0x06638570, 0x06800000)
  to   space 4032K,   0% used [0x06800000, 0x06800000, 0x06bf0000)
 tenured generation   total 57224K, used 40267K [0x0eea0000, 0x12682000, 0x24400000)
   the space 57224K,  70% used [0x0eea0000, 0x115f2ca0, 0x115f2e00, 0x12682000)
 Metaspace       used 31836K, capacity 33329K, committed 33408K, reserved 34176K
5.742: [GC (Allocation Failure) 5.742: [DefNew: 35041K->4031K(36864K), 0.0193873 secs] 75308K->46613K(94088K), 0.0194649 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 

 

  然而這種調優粒度太粗,服務器上程序須要定位更精確的時間,因此須要更多參數來調試。

  先寫到這裏吧。  

  玄不救非,氪不改命。

 

Reference

  1.http://timyang.net/java/java_gc_tunning/

  2.ImportNew

相關文章
相關標籤/搜索