當我們診斷Java應用程序的問題時,能夠查到垃圾回收的狀況是非常有幫助的。一個基本的最基礎的方法是開啓垃圾回收日誌。
也許你已經知道了,如果我們把下面的參數加到java啓動命令行中,
-Xloggc:<file_name> –XX:+PrintGCDetails -XX:+PrintGCDateStamps
JVM就會將垃圾回收信息寫到-Xloggc設置的文件中。日誌格式如下:
2010-04-22T18:12:27.796+0200: 22.317: [GC 59030K->52906K(97244K), 0.0019061 secs]
2010-04-22T18:12:27.828+0200: 22.348: [GC 59114K->52749K(97244K), 0.0021595 secs]
2010-04-22T18:12:27.859+0200: 22.380: [GC 58957K->53335K(97244K), 0.0022615 secs]
2010-04-22T18:12:27.890+0200: 22.409: [GC 59543K->53385K(97244K), 0.0024157 secs]
加粗的部分顯示了垃圾回收時間的開始日期及時間。
不幸的是-XX:+PrintGCDateStamps參數只有Java 6u4及以後版本的JVM纔可用。所以,如果我們很不幸而且我們的應用程序跑在就版本的JVM上,我們就被迫使用,
-Xloggc:<file> –XX:+PrintGCDetails
這樣,得到的垃圾回收信息如下:
22.317: [GC 59030K->52906K(97244K), 0.0019061 secs]
22.348: [GC 59114K->52749K(97244K), 0.0021595 secs]
22.380: [GC 58957K->53335K(97244K), 0.0022615 secs]
22.409: [GC 59543K->53385K(97244K), 0.0024157 secs]
現在,加粗的數字表示從JVM啓動時間開始的秒間隔。
這種情況下就很難把GC事件和其他日誌文件中的信息關聯到一起了:(
有沒有簡單的辦法來處理gc日誌文件並從秒間隔中關聯出日期和時間呢?看起來是可以的,但是秒間隔從什麼時候開始呢?換句話說,我們怎麼得知JVM的啓動日期和時間呢?
爲了使用最最基礎的方法,我們應該從同一個GC日誌文件中分析出日期和時間。這樣我們就要用到文件的屬性了。我們有如下不同的選擇:
Unix | Windows |
Access time | Access time |
Change time | Creation time |
Modify time | Modify time |
我們拋棄掉access time、cheng time及creation time,因爲他們不是在所有平臺上都可用的。這樣我們只剩下修改時間(modify time),表示文件最後一次修改的時間。
在Windows上,當文件被拷貝到其他地方時,修改時間是保持不變的。但是當我們在Unix上拷貝GC日誌文件時,我們需要使用-p選項來保持時間戳屬性。
GC日誌文件的最後修改時間應該和日誌文件中最後一個記錄的GC時間的時間戳保持一致。好吧,更精確點,由於日誌行是在GC執行時一點一點寫的,最後修改時間應該是確切等於最後的秒間隔加上GC的執行時間。
22.409: [GC 59543K->53385K(97244K), 0.0024157 secs]
在我們的方法中由於不需要精確時間,我們拋棄執行時間,進而得到每次垃圾回收時間發生的粗略時間。然而,我們必須注意在大的heap中,有時間GC的執行時間可以長達幾秒。
當我們最近在一個客戶那裏體驗這個方案時,我們需要快速開發一個簡單的輕便的腳本,所以就選擇Python來實現這個功能。你已經知道了我們不僅僅用Java吧。
- #!/usr/bin/env python
- import sys, os, datetime
- # true if string is a positive float
- def validSeconds(str_sec):
- try:
- return 0 < float(str_sec)
- except ValueError:
- return False
- # show usage
- if len(sys.argv) < 2:
- print "Usage: %s <gc.log>" % (sys.argv[0])
- sys.exit(1)
- file_str = sys.argv[1]
- lastmod_date = datetime.datetime.fromtimestamp(os.path.getmtime(file_str))
- file = open(file_str, 'r')
- lines = file.readlines()
- file.close()
- # get last elapsed time
- for line in reversed(lines):
- parts = line.split(':')
- if validSeconds(parts[0]):
- break
- # calculate start time
- start_date = lastmod_date - datetime.timedelta(seconds=float(parts[0]))
- # print file prepending human readable time where appropiate
- for line in lines:
- parts = line.split(':')
- if not validSeconds(parts[0]):
- print line.rstrip()
- continue
- line_date = start_date + datetime.timedelta(seconds=float(parts[0]))
- print "%s: %s" % (line_date.isoformat(), line.rstrip())
這個腳本的輸出可以重定向到另一個文件中,在哪裏可以得到:
2010-04-22T18:12:27.796375: 22.317: [GC 59030K->52906K(97244K), 0.0019061 secs]
2010-04-22T18:12:27.828375: 22.348: [GC 59114K->52749K(97244K), 0.0021595 secs]
2010-04-22T18:12:27.859375: 22.380: [GC 58957K->53335K(97244K), 0.0022615 secs]
2010-04-22T18:12:27.890375: 22.409: [GC 59543K->53385K(97244K), 0.0024157 secs]
你可能注意到了日期的格式並不是和-XX:+PrintGCDateStamps參數100%的一致。但這足以看出每個GC時間發生的時間。
原文:http://www.theserverlabs.com/blog/2010/05/26/human-readable-jvm-gc-timestamps/
PS:翻譯這篇blog主要是想和大家分享這個python腳本,非常的使用。在運維類似於Hadoop這樣的分佈式大集羣試,master的GC日誌是一定要打開,因爲各種各樣的問題都可能和GC有關。但由於某些原因,如JDK版本或歷史原因,可能配置了–XX:+PrintGCDetails
而不是-XX:+PrintGCDateStamps,這樣就非常需要本文的腳本來轉換時間格式。當然最好是配上-XX:+PrintGCDateStamps參數。