隨著時間的推移,IBM 為它的 Java 運行時實現開發了許多監視和問題診斷設施。利用這些工具,IBM 支持團隊、Java 應用程序開發人員和生產操作人員可以診斷和解決在 Java 開發中遇到的問題。
本文討論三種主要的設施,因為它們是在 Java 技術的 IBM 實現的最新版本中實現的:跟蹤引擎、轉儲引擎和 DTFJ 工具 API。它們都有助于 Java 開發人員判斷問題的根源。
![]() |
|
跟蹤引擎
在判斷軟件的問題時,跟蹤信息是一種強大的工具:可以使用它有效地研究問題場景(比如功能性錯誤、競爭狀態和性能問題),而且它非常有助于了解程序的流程。
IBM 在 SDK 1.2.2 中首次在它的 Java 運行時實現中引入了跟蹤引擎,幫助 IBM 開發團隊診斷 Java 虛擬機(JVM)的缺陷。這種跟蹤設施的目的是為虛擬機本身提供一個低開銷、高性能、可配置的跟蹤機制。在后續的版本中,進行了顯著的調整和改進;IBM SDK 的當前版本提供一個高性能的引擎,它能夠捕捉 JVM、Java Class Libraries(JCL)和部署到運行時中的任何 Java 應用程序代碼的跟蹤數據,而不需要任何其他設施。
![]() |
|
激活和控制跟蹤
可以通過多種機制激活和控制跟蹤引擎:
-Xtrace
com.ibm.jvm.Trace
API,使用 Java 代碼進行動態控制 控制跟蹤的主要方法是使用命令行選項 -Xtrace
,或者在選項集比較長或復雜的情況下,使用可選的跟蹤屬性文件。
-Xtrace
選項由一系列標志或標志-值對組成,這些設置用來決定跟蹤應該寫到 stderr
、內部緩沖區還是二進制文件;是啟用方法跟蹤、JVM 跟蹤,還是兩者都啟用;應該跟蹤哪些跟蹤點;是跟蹤對跟蹤點的任何修改,還是在發生事件時觸發轉儲。
激活跟蹤的基本知識
在使用 IBM 的跟蹤設施時,需要決定的第一件事是應該將跟蹤輸出定向到哪個目的地。表 1 簡要描述這些目的地以及將多少跟蹤點數據發送給它。例如,print
將所有跟蹤數據定向到 stderr
,minimal
將每個跟蹤點的數據子集定向到內存緩沖區,然后又可以使用 output
選項將這些緩沖區中的數據捕捉到文件中。
關鍵字 | 功能 |
---|---|
minimal |
將選擇的跟蹤點(只有標識符和時間戳)定向到核心緩沖區。不記錄相關聯的跟蹤數據。 |
maximal |
將選擇的跟蹤點(標識符和時間戳以及相關聯的數據)定向到核心緩沖區。 |
count |
統計在 JVM 的生命期內調用選擇的跟蹤點的次數。 |
print |
將選擇的跟蹤點定向到 stderr ,不進行縮進。 |
iprint |
將選擇的跟蹤點定向到 stderr ,進行縮進。 |
external |
將選擇的跟蹤點定向到 JVMRI 監聽器。 |
exception |
將選擇的跟蹤點定向到為異常保留的核心緩沖區。 |
應該將每個關鍵字的值設置為所需的跟蹤點。例如:
-Xtrace:maximal=all
將來自所有 JVM 跟蹤點的所有信息記錄到內部回繞緩沖區中。-Xtrace:iprint=awt
將所有 JVM 內部 AWT 跟蹤點記錄到 stderr
,在進入和退出時進行縮進。-Xtrace:iprint=mt
激活方法跟蹤并將輸出發送到 stderr
,進行縮進。 僅僅使用表 1 中的選項并不會生成任何輸出;必須單獨提供要跟蹤的方法名。注意,IBM Diagnostics Guide 的第 32 章 “Tracing Java applications and the JVM” 詳細討論了所有跟蹤選項(見 參考資料)。
將跟蹤數據放進內部緩沖區中
使用存儲內緩沖區進行跟蹤是非常高效的,因為在探測到問題或者使用 API 將緩沖區寫入文件之前,不執行顯式的 I/O。緩沖區被分配給每個線程,這防止線程之間發生沖突并防止各個線程的跟蹤數據相互干擾。例如,如果某個線程沒有被調度,當轉儲緩沖區時它的跟蹤信息仍然是可用的。
要查看跟蹤數據,必須轉儲緩沖區并進行格式化。當發生以下情況時,自動進行緩沖區的轉儲:
com.ibm.jvm.Trace.snap()
Java API TraceSnap
函數 將跟蹤數據放進文件中
可以連續地將跟蹤數據寫到文件中,作為存儲內跟蹤的擴展;但是在這種情況下不是為每個線程分配一個緩沖區,而是至少分配兩個。當一個跟蹤緩沖區滿了時,將它寫到文件系統中,這使線程能夠連續運行。根據跟蹤量、緩沖區大小和輸出設備的帶寬,可以將多個緩沖區分配給給定的線程,從而與生成跟蹤數據的速度相匹配。
要將 minimal
或 maximal
跟蹤選項的輸出寫到文件中,應該使用 output
關鍵字,對于 exception 選項使用 exception.output
關鍵字:
-Xtrace:maximal=all,output=trace.out
將跟蹤數據寫到文件 trace.out 中。 -Xtrace:maximal=all,output={trace.out,5m}
將跟蹤數據寫到文件 trace.out 中,當文件達到 5MB 時進行回繞。 -Xtrace:maximal=all,output={trace#.out,5m,5}
將跟蹤數據依次寫到 5 個文件中,每個文件 5MB,#
用文件的序號代替。在這個示例中,創建文件 trace0.out 到 trace4.out,每個文件包含最近的 5MB 跟蹤數據。當 5 個文件都填滿時,JVM 依次覆蓋 trace0.out 到 trace4.out。這個選項可以創建的最大文件數是 36 個,#
字符被替換為 0 到 9,然后是 A 到 Z。 還可以在文件名中進行以下替換:
%p
:Java 進程的 ID。 %d
:yyyymmdd 格式的當前日期。 %t
:hhmmss 格式的當前時間。 對跟蹤文件進行格式化
跟蹤格式化器(trace formatter) 是一個可以在任何平臺上運行的 Java 程序,可以對來自任何平臺的跟蹤文件進行格式化。IBM SDK 在 core.jar 中提供了這個格式化器,它還需要一個稱為 TraceFormat.dat 的文件,其中包含格式化模板。這個文件在 jre/lib 中??梢杂靡韵旅钚袉痈櫢袷交鳎?
|
在這里,com.ibm.jvm.format.TraceFormat
是跟蹤格式化器類,input_file
是要進行格式化的二進制跟蹤文件的名稱,output_file
是可選的輸出文件名。如果沒有指定輸出文件,那么默認的輸出文件名是輸入文件名加上 .fmt。
動態記錄器
IBM VM 跟蹤設施包含一個動態記錄器(flight recorder),它連續地將來自關鍵跟蹤點子集的數據捕捉到內存緩沖區中。當出現運行時問題時捕捉這些緩沖區,可以用來診斷問題并分析 VM 的歷史。VM 初始化過程用一小組跟蹤點啟動跟蹤,這些跟蹤點被捕捉到回繞式存儲內緩沖區中??梢允褂眠@些信息初步診斷 Java 運行時中的任何問題,并確保 -verbose:gc
選項提供的數據子集總是可用的。垃圾收集數據也出現在 Java 轉儲文件中。
內部的動態記錄器使用一個命令行選項:
|
如果在命令行上指定 -Xtrace
,或者在屬性文件中設置它,那么清除激活的跟蹤點集。
方法跟蹤
可以利用 Java 方法跟蹤來跟蹤每個線程對方法的調用,包括進入方法和退出方法,這種跟蹤針對 Java 運行時的 IBM 實現上運行的任何代碼進行。這不需要對 Java 代碼進行任何手工處理,可以使用它跟蹤 JCL、第三方包或應用程序代碼。
方法跟蹤功能尤其適合調試發生競爭狀態和在方法之間傳遞了不合適的參數而導致異常的情況。由于跟蹤時間戳具有毫秒級的精度,方法跟蹤還可以用來調試性能問題。
在命令行上調用方法跟蹤的辦法是,添加 methods
關鍵字標志并將 mt
設置給目的地關鍵字之一(maximal
、minimal
、print
)。methods
關鍵字允許按照類、方法名或這兩者選擇要跟蹤的方法??梢允褂猛ㄅ浞腿》床僮鞣?!
建立復雜的選擇條件。例如:
-Xtrace:print=mt,methods={*.*,!java/lang/*.*}
:對于除了 java.lang
包中的方法和類之外的所有方法和類,將方法跟蹤寫到 stderr
中。-Xtrace:maximal=mt,output=trace.out,methods={tests/mytest/*.*}
:對于 tests.mytest
包中的所有方法,將方法跟蹤寫到文件中。(注意,這個選項只選擇要跟蹤的方法。) 在發生跟蹤事件時觸發
IBM 跟蹤引擎最強大的特性之一是它能夠在發生跟蹤事件時觸發,這有助于創建目的明確的跟蹤輸出并減少產生的跟蹤數據量。這會提高被調試的應用程序的性能(由于開銷大大降低了)和解釋數據的速度(由于多余的數據減少了)。
跟蹤引擎能夠在任何給定的跟蹤點上觸發,包括 VM 內部的跟蹤點或 Java 方法,而且在發生事件時可以執行許多操作,見表 2:
關鍵字 | 功能 |
---|---|
suspend |
暫停所有 跟蹤(特殊跟蹤點除外)。 |
resume |
恢復所有 跟蹤(由 resumecount 屬性和 Trace.suspendThis() 調用暫停的線程除外)。 |
suspendthis |
增加這個線程的暫停計數。非零的暫停計數會停止這個線程的所有跟蹤。 |
resumethis |
減少這個線程的暫停計數(如果這個值大于零的話)。如果暫停計數到達零,那么這個線程的跟蹤就會恢復。 |
sysdump |
生成非破壞性的系統轉儲。 |
javadump |
生成 Java 轉儲。 |
heapdump |
生成堆轉儲。 |
snap |
將所有激活的跟蹤緩沖區寫到當前工作目錄中的一個文件中。 |
可以使用 trigger
命令行關鍵字激活觸發跟蹤,這個關鍵字決定在發生事件時執行表 2 中的哪些操作。注意,觸發選項控制其他跟蹤屬性已經選擇的跟蹤數據是正常生成,還是被阻塞。
使用以下格式指定方法事件上的觸發:
|
當進入與 method spec 匹配的任何方法時,執行 entry action。當退出方法時,執行 exit action。如果指定了 delay count,那么只有當進入和退出的次數大于 delay count 時才執行 entry action 和 exit action。如果指定了 match count,那么操作的執行次數不超過這個值。請考慮以下示例:
|
這在第一次(而且只在第一次)調用 StackOverflowError
方法時(進行調用的是 <clinit>
方法),創建一個非破壞性的系統轉儲。
可以使用 suspend
和 resume
選項并結合 resumecount
或 suspendcount
關鍵字來暫停和恢復單獨的線程或所有線程:
|
這些選項在調用 HelloWorld.main()
時開始跟蹤所有線程,在 HelloWorld.main()
返回時停止跟蹤。這實際上意味著在 Java 運行時啟動時不進行跟蹤,只在 HelloWorld 應用程序運行期間生成跟蹤數據。
用跟蹤引擎能夠實現什么?
可以使用跟蹤引擎為 Java 運行時本身或其中運行的應用程序代碼中的任何問題生成數據流或歷史數據。這些歷史數據加上轉儲引擎生成的狀態數據能夠幫助開發人員了解和調試許多問題。
![]() ![]() |
![]()
|
轉儲引擎
Java 運行時的 IBM 實現中內置了轉儲引擎,它能夠提供大多數必需的數據,幫助 IBM 支持團隊對 Java 運行時本身或 IBM SDK 提供的 JCL 中的問題進行診斷。轉儲引擎的默認設置在發生特定事件時觸發許多不同類型的轉儲,可以對這些轉儲進行后期處理來判斷許多問題的原因。
還可以使用許多轉儲和事件來診斷 Java 應用程序中的問題,因為用來診斷 JCL 中的問題的過程同樣可以用來診斷其他 Java 類中的問題。
轉儲類型
IBM 轉儲引擎可以生成 4 種類型的轉儲(在 z/OS® 上有 5 種),如果需要的話,還能夠在單獨的進程中執行一個工具。每個轉儲類型本身都是非破壞性的,但是如果是由故障事件(比如 SIGSEGV/GPF)造成的,就會變成破壞性的。表 3 描述可用的轉儲類型:
關鍵字 | 轉儲類型 | 說明 |
---|---|---|
java |
Java 轉儲 | 包含環境、鎖、線程堆棧和類信息的狀態報告。 |
heap |
堆轉儲 | 這種轉儲包含 Java 堆上每個 Object 的大小和引用細節。 |
snap |
Snap 轉儲 | 寫到文件中的跟蹤緩沖區內容。 |
system |
系統轉儲 | 進程映像,采用操作系統的一般格式(核心文件、微轉儲或事務轉儲)。 |
ceedump |
CEEDUMP | z/OS 特有的線程堆棧和寄存器匯總文件。 |
tool |
工具代理 | 使用提供的命令行執行一個預定義的工具。 |
轉儲事件
IBM 轉儲引擎能夠在發生表 4 中的事件時生成任何或所有類型的轉儲:
事件 | 說明 |
---|---|
gpf |
發生未預料到的崩潰,比如 SIGSEGV 或 SIGILL。 |
user |
發生 SIGQUIT 信號(Windows 上的 Control+Break,Linux 上的 Control+\,z/OS 上的 Control+V)。 |
vmstart |
VM 完成初始化。 |
vmstop |
VM 將要關閉。 |
load |
裝載了一個新類。 |
unload |
卸載了一個類裝載器。 |
throw |
拋出了一個 Java 異常。 |
catch |
捕捉到了一個 Java 異常。 |
uncaught |
有一個 Java 異常未被應用程序處理。 |
thrstart |
啟動了一個新線程。 |
thrstop |
停止了一個現有線程。 |
blocked |
一個線程被阻塞,進入監視器。 |
fullgc |
啟動垃圾收集。 |
這些事件本身為指定何時生成每種轉儲提供了很大的靈活性,但是如果結合使用轉儲過濾器 的話,靈活性還會大大提高??梢栽诿糠N事件上添加一個過濾器,從而以更細的粒度控制何時創建轉儲。例如,可以在 throw
、catch
和 uncaught
事件上添加異常名或錯誤名。
設置轉儲選項并修改默認設置
使用 -Xdump
命令行選項和一系列標志來設置各種轉儲選項??梢允褂?-Xdump:what
查看默認的轉儲選項,見清單 1:
|
可以通過修改語法來添加其他轉儲。要在發生未捕捉的套接字異常時生成 Java 轉儲,使用以下語法:
|
要刪除所有堆轉儲,使用以下語法:
|
使用轉儲引擎能夠實現什么?
可以使用轉儲引擎的功能解決 IBM SDK 本身中的問題;更重要的是,可以利用它們解決 Java 應用程序中的問題。在發生 OutOfMemoryErrors
時能夠生成 Java 轉儲文件和堆轉儲,因此能夠診斷內存泄漏并分析任何大對象的堆棧。能夠在發生其他異常時生成 Java 轉儲文件,因此能夠使用轉儲中的線程堆棧數據來調試潛在的競爭狀態。
另外,在發生各種事件時能夠創建非破壞性的系統轉儲,這意味著可以使用 DTFJ API 研究在發生事件時 Java 應用程序的任何部分的狀態。
![]() ![]() |
![]()
|
Diagnostic Toolkit and Framework for Java
DTFJ API 是一個基于 Java 的 API,工具的編寫者可以使用它訪問關于 Java 進程的信息,這只需要有進程映像的快照(例如,系統轉儲),工具的編寫者不需要了解各種系統轉儲格式以及 Java 對象和其他 Java 結構在內存中的布局方式。
正如前面提到的,Java 運行時的 IBM 實現能夠使用跟蹤或轉儲引擎創建非破壞性的系統轉儲。另外,還可以使用 com.ibm.jvm.Dump.SystemDump()
靜態方法創建非破壞性的系統轉儲。還可以使用操作系統工具獲得同樣的結果,例如 AIX® 上的 gencore
或 Linux 上的 gcore
。
創建非破壞性的系統轉儲使工具能夠使用 DTFJ API 從正在運行的系統獲得信息,還可以對發生故障和已經關閉的系統進行分析。
體系結構
DTFJ API 是一個分層的接口,它獨立于運行時實現:這個 API 本身可以用于多種操作系統和硬件平臺、多種虛擬機實現和多種語言。DTFJ API 中包含的基本擴展集針對的是 Java 運行時,因此使工具的編寫者能夠了解和探察 JVM 數據結構,Java 運行時的 IBM 實現附帶的 DTFJ 實現能夠提供關于這些運行時中的數據結構的信息。
這個 API 本身受到了 Reflection API 的深刻影響,并結合了 Java 進程的一個層次化視圖,這個視圖使用 Iterator
訪問從高層對象到更特定對象的各個對象。這提供了許多可用的數據對象,從進程的 Image
到單獨的 JavaField
和 JavaMethod
對象,可以探察這些對象來獲得在建立系統轉儲時它們包含的數據。圖 1 給出了 DTFJ API 可以了解和探察的一些數據對象:
運行 JExtract
因為各種操作系統生成不同的系統轉儲格式,而且隨著時間的推移內部 Java 運行時數據結構可能會發生一些必要的改變,所以需要對系統轉儲運行一個稱為 JExtract 的實用程序,然后才能使用 DTFJ API 訪問系統轉儲。這個操作需要由生成系統轉儲時運行的相同 Java 運行時版本來執行,而且應該在同一個系統上執行。
JExtract 實用程序了解系統轉儲和 Java 運行時內部數據結構的格式。它利用這一知識創建一個 XML 描述文件,這個文件為系統轉儲文件提供索引,指出各個數據結構的位置。然后,DTFJ 就可以結合使用系統轉儲和 JExtract 生成的 XML 文件來生成工具所請求的信息。
盡管 JExtract 是系統轉儲的后處理程序,但是可以使用轉儲引擎的 tool
選項在創建系統轉儲之后自動調用 JExtract。例如,為了在發生 OutOfMemoryErrors
時創建系統轉儲,使用以下語法:
|
可以使用 DTFJ API 實現什么?
可以使用 DTFJ API 訪問系統轉儲中的大量信息。這包括關于運行進程的平臺的信息:物理內存、CPU 數量和類型、庫、命令行、線程堆棧和寄存器。它還可以提供關于 Java 運行時和 Java 應用程序的狀態的信息,包括類裝載器、線程、監視器、堆、對象、Java 線程、方法、編譯的代碼和字段及其值。
因為提供的數據范圍非常廣泛,DTFJ API 為創建各種工具提供了很大的靈活性。例如,在比較簡單的層面上,它使工具能夠查明各個緩存的大小和內容,從而更有效地調整這些緩存所需要的 Java 堆內存量。
開始使用 DTFJ
創建基于 DTFJ 的工具的第一步是獲得與系統轉儲相關的 Image
對象,然后從 ImageAddressSpace
獲得 ImageProcess
對象,見清單 2:
|
在大多數平臺上,映像中只有一個 ImageAddressSpace
和 ImageProcess
對象;但是,大型機操作系統可能有多個實例。
獲得 ImageProcess
對象之后,就可以訪問本機線程,見清單 3:
|
還可以訪問各個 ImageModule
(庫)對象,見清單 4:
|
可以從 ImageProcess
對象獲得 JavaRuntime
,見清單 5:
|
這樣就可以訪問所有 Java 結構。
獲得了 JavaRuntime
對象之后,就可以編寫工具來探察正在運行的任何 Java 應用程序。清單 6 中的簡單示例演示了如何遍歷 Java 堆上的所有對象并統計每種類型的對象的數量:
|
![]() ![]() |
![]()
|
結束語
本文討論的所有功能都可以幫助您診斷和解決在 Java 部署中遇到的開發和生產問題。結合使用這三種主要的設施來生成歷史跟蹤數據和詳細的狀態數據,再用簡單的 API 訪問狀態數據,就可以以強大且靈活的方式探察 Java 應用程序并解決問題。
本文結束了對 Java 虛擬機的 IBM 實現中主要改進和改變的討論。具體地說,我們討論了內存管理、類共享和應用程序監視,描述了如何利用這些功能改進 Java 應用程序的性能和可用性。關于這些改進和其他改進的更多信息可以在 IBM Diagnostics Guide 中找到,還可以通過 IBM Runtimes and SDKs 論壇進行反饋和討論(見 參考資料 中的鏈接)。
在這個系列的最后一篇文章中,Java 安全開發團隊將討論 IBM 對 Java 平臺的安全改進。那篇文章將介紹每個安全組件以及它們提供的功能。
原文轉自:http://www.anti-gravitydesign.com