1. 程式人生 > >用Java獲取full GC的次數

用Java獲取full GC的次數

大家如果熟悉JDK 6的內建工具,或許已經知道可以通過jstat工具很輕鬆的從外部得知一個Java程序的GC統計資訊,其中就包括了full GC的次數。 
假定我們相信jstat的資料是準確的,那麼只要跟它從同一來源獲取資料就可以保證我們拿到正確的full GC次數資訊了。 
檢視OpenJDK 6中jstat的一個原始檔,jdk/src/share/classes/sun/tools/jstat/resources/jstat_options,可以看到jstat -gcutil輸出的YGC(young GC)與FGC(full GC)值分別是從下面兩個定義而來的: 
Text程式碼  收藏程式碼
  1. column {  
  2.   header "^YGC^"    /* Young Generation Collections */  
  3.   data sun.gc.collector.0.invocations  
  4.   align right  
  5.   width 6  
  6.   format "0"  
  7. }  
  8. column {  
  9.   header "^FGC^"    /* Full Collections */  
  10.   data sun.gc.collector.1.invocations  
  11.   align right  
  12.   width 5  
  13.   scale raw  
  14.   format "0"  
  15. }  

也就是說,在Oracle (Sun) HotSpot上,通過
jvmstat API,找到名字為 "sun.gc.collector.0.invocations" 與"sun.gc.collector.1.invocations" 的Monitor物件,我們就可以拿到YGC與FGC對應的值了。別的JVM即便支援該API,Monitor的名字也可能會不同;在Oracle (BEA) JRockit R28上,兩者對應的名字分別為"jrockit.gc.latest.yc.number" 與 "jrockit.gc.latest.oc.number" 。 

在底層,HotSpot通過perfData介面來提供這些資料;註冊資料的實現在services目錄裡。 


OpenJDK官網上也有一個相關文件:HotSpot Jvmstat Performance Counters

提一下jvmstat文件上說的一個注意點: 
引用 The instrumented HotSpot JVM exports a set of instrumentation objects, or counters as they are typically called. The set of counters exported by a JVM is not static, as a JVM may create certain counters only when appropriate arguments are specified on the command line. Furthermore, different versions of a JVM may export very different sets of instrumentation. The names of these counters and the data structures used to represent them are considered private, uncommitted interfaces to the HotSpot JVM. Users should not become dependent on any counter names, particularly those that start with prefixes other than "java.".
也就是說我們最好別依賴這些計數器的名字。不過反正這帖只是介紹些hack的辦法而已,不管了 

下面用Groovy程式碼來演示一下。 
使用jvmstat API需要指定vmid,對多數系統上本地Java程序的VMID就是PID。這裡正好用上以前一帖介紹的獲取程序自己的PID的方式。 

Groovy程式碼  收藏程式碼
  1. import java.lang.management.ManagementFactory  
  2. import sun.jvmstat.monitor.*;  
  3. class Runtimes {  
  4.   static int getOwnPid() {  
  5.     def name = ManagementFactory.runtimeMXBean.name  
  6.     name[0..<name.indexOf('@')] as int  
  7.   }  
  8. }  
  9. class GCStats {  
  10.   // Oracle (Sun) HotSpot  
  11.   static final String YOUNG_GC_MONITOR_NAME = 'sun.gc.collector.0.invocations'  
  12.   static final String FULL_GC_MONITOR_NAME  = 'sun.gc.collector.1.invocations'  
  13.   // Oracle (BEA) JRockit  
  14.   // static final String YOUNG_GC_MONITOR_NAME = 'jrockit.gc.latest.yc.number'  
  15.   // static final String FULL_GC_MONITOR_NAME  = 'jrockit.gc.latest.oc.number'  
  16.   static final Monitor youngGCMonitor;  
  17.   static final Monitor fullGCMonitor;  
  18.   static {  
  19.     def vmId     = new VmIdentifier(Runtimes.ownPid as String)  
  20.     def interval = 0  
  21.     def monitoredHost = MonitoredHost.getMonitoredHost(vmId)  
  22.     def monitoredVm = monitoredHost.getMonitoredVm(vmId, interval)  
  23.     youngGCMonitor = monitoredVm.findByName(YOUNG_GC_MONITOR_NAME)  
  24.     fullGCMonitor = monitoredVm.findByName(FULL_GC_MONITOR_NAME)  
  25.   }  
  26.   static int getYoungGCCount() {  
  27.     youngGCMonitor.value  
  28.   }  
  29.   static int getFullGCCount() {  
  30.     fullGCMonitor.value  
  31.   }  
  32. }  

可以看到,要獲取young GC與full GC次數的讀數很簡單,找到合適的Monitor物件後,每次讀一下value屬性就好了。 
在Groovy shell裡演示一下使用情況,在Sun JDK 6 update 20/Windows XP SP3上跑: 
Groovysh程式碼  收藏程式碼
  1. D:\>\sdk\groovy-1.7.2\bin\groovysh  
  2. Groovy Shell (1.7.2, JVM: 1.6.0_20)  
  3. Type 'help' or '\h' for help.  
  4. --------------------------------------------------  
  5. groovy:000> GCStats.fullGCCount  
  6. ===> 0  
  7. groovy:000> System.gc()  
  8. ===> null  
  9. groovy:000> GCStats.fullGCCount  
  10. ===> 1  
  11. groovy:000> System.gc(); System.gc(); System.gc()  
  12. ===> null  
  13. groovy:000> GCStats.fullGCCount  
  14. ===> 4  
  15. groovy:000> GCStats.youngGCCount  
  16. ===> 9  
  17. groovy:000> System.gc(); System.gc(); System.gc()  
  18. ===> null  
  19. groovy:000> GCStats.youngGCCount  
  20. ===> 9  
  21. groovy:000> GCStats.fullGCCount  
  22. ===> 7  
  23. groovy:000> quit  


這次也順便演示一下在Oracle JRockit R28/Windows XP SP3上跑: 
Groovysh程式碼  收藏程式碼
  1. D:\>\sdk\groovy-1.7.2\bin\groovysh  
  2. Groovy Shell (1.7.2, JVM: 1.6.0_17)  
  3. Type 'help' or '\h' for help.  
  4. --------------------------------------------------  
  5. groovy:000> GCStats  
  6. ===> class GCStats  
  7. groovy:000> GCStats.youngGCCount  
  8. ===> 1  
  9. groovy:000> System.gc()  
  10. ===> null  
  11. groovy:000> GCStats.youngGCCount  
  12. ===> 2  
  13. groovy:000> System.gc(); System.gc(); System.gc()  
  14. ===> null  
  15. groovy:000> GCStats.youngGCCount  
  16. ===> 5  
  17. groovy:000> GCStats.fullGCCount  
  18. ===> 0  
  19. groovy:000> quit  

可以看到System.gc()引發的是young GC而不是full GC吧? ^_^