使用 Extensible Verbose Toolkit 進行垃圾收集
提高應用程序性能、優化垃圾收集以及發現應用程序問題
簡介:Extensible Verbose Toolkit 是 IBM 的新工具®,其設計目標為幫助診斷和分析與內存 有關的 Java™ 性能問題 。本文是四篇系列文章的第二篇,介紹如何獲得和使用該套件,並演示如 何使用它來快速診斷某些常見的問題。
鑒於以下這些原因,您可能需要仔細查看應用程序中的垃 圾收集 (GC)。您可能關心應用程序的內存使用模式:是否使用了太多內存?是否發生內存洩露?內存使 用情況是否能長期維持? 您還可能對如何提高應用程序執行速度感興趣。垃圾收集對應用程序的性能具 有很大影響。很多人都知道,配置不佳的 GC 會使用大量資源,而且還會降低應用程序的速度。但反過來 也正確:明智地選擇垃圾收集參數實際上會使應用程序運行速度更快。
在周期較短的 Java 應用 程序或性能不是非常重要的應用程序中,可以恰當地忽略 GC。在其他情況下,該工具可以從詳細的 GC 日志中更加輕松地獲得所需的信息。該工具可以顯示堆中正在發生的變化,從而更加容易確定模式,它它 甚至可以為您指出某些模式並且給出調整建議。
EVTK 屬於 IBM 的新工具套件,它分析詳細的 GC 日志以幫助提供對內存管理問題的深入分析。在本文中,您將了解 EVTK 的功能並查看 EVTK 可以幫 助您診斷內存問題的示例場景。
EVTK 可以處理所有版本為 1.4.2 或更高版本的 IBM JRE 日志。 還可以實時查看 IBM WebSphere® 中的日志。借助這種工具,您可以同時對比多個日志、放大日志的 特定區域、篩選數據以及在單位范圍內顯示。 EVTK 顯示示例如圖 1 所示:
圖 1. EVTK 顯示示 例
啟用詳細的 GC 日志記錄
如果想生成用於分析的日志,必須對應用程序啟用詳細的 GC 日志記錄。 也可以通過 -verbose:GC 虛擬 機 (VM) 標記或通過版本為 5.0 或更高版本的 IBM VM 的 -XverboseGClog:file 命令。其中, file 是您所選擇的日志文件的名稱。-XverboseGClog 選項可用時,首選該選項。 詳細 GC 通常對應用 程序性能的影響比較小。
下載和安裝 EVTK
EVTK 在 IBM Support Assistant 中可以免費下載。如果您尚未安裝 IBM Support Assistant,則需 要先下載。安裝 IBM Support Assistant 之後,您可以安裝 EVTK 插件。可以在 IBM Support Assistant 的 Updater 頁面上下載 EVTK,它位於 Common Component Tools 部分中的 New Plug-ins 選 項卡下,如圖 2 所示:
圖 2. 安裝 EVTK
您還需要通過安裝產品插件讓 IBM Support Assistant 知道您使用的產品包括 JVM,如圖 3 所示。 例如,您可能想從選擇 Others 部分選擇一個用於 Java 的開發人員套件或從 WebSphere 部分中選擇一 個 WebSphere 產品。
圖 3. 安裝產品插件
安裝插件之後,需要重新啟動 IBM Support Assistant。可以在 Tools 頁面上啟動,如圖 4 所示:
圖 4. 啟動 EVTK
常見任務
現在讓我們使用 EVTK 執行一些基本的日志分析任務。
打開要進行分析的日志
若要使用 EVTK 分析詳細的 GC 日志,請啟動 EVTK,然後從 File 菜單中選擇 Open File。EVTK 將 在具有四個選項卡的編輯器中打開該日志,如圖 5 所示。您可以從運行的應用程序中打開日志,但是 EVTK 不會自動更新顯示。若要刷新顯示,請單擊 Reset Axes 按鈕。
圖 5. EVTK 編輯器中的選項卡
選項卡如下:
用文件名進行標記的選項卡顯示日志本身的文本。如果日志很大,則 EVTK 將不會顯示所有文本,而 仍然要對整個日志進行解析。
Data 選項卡顯示 EVTK 生成數據的原始視圖。該數據適合剪切並粘貼到電子表格中。
Line Plot 選項卡顯示數據。
Report 選項卡顯示 EVTK 對該數據的報告、每個所選字段的摘要、整個日志的表格式摘要以及一系列 調整建議。
VGC Data 菜單,如圖 6 所示,顯示可以查看的所有字段。灰顯的字段是 EVTK 已經進行了查找但沒 有在當前日志中找到的字段。如果未選擇 Summary 字段,則可以選擇該字段來啟用表格式的摘要。同樣 ,還可以啟用 Tuning Recommendation 以獲得建議。
圖 6. VGC Data 菜單
對比多個文件
EVTK 允許您同時分析多個文件。這對於評估性能更改效果非常方便。圖 7 顯示使用三個 GC 策略執 行固定工作負載的應用程序。(這種情況下,應用程序本身便是 EVTK。)實線是 gencon GC 策略,點線 是 optavgpause 策略,虛線是 optthruput 策略。EVTK 基於日志文件名稱得出行標簽。
圖 7. 三種不同的垃圾收集策略的堆使用情況和暫停時間
這種情況下,根據所有評測條件,gencon 模式明顯最佳。它完成任務的速度最快、過程中使用的堆最 少並且 GC 暫停時間更短。但 gencon 策略不是默認的策略;在大多數情況下,optthruput 是默認策略 ,它勝過 gencon。正如此例所示,盡管它並不總是勝過 gencon 策略,因此當使用不同的 GC 策略時, 應該查看應用程序如何改變行為。通常,非常簡單的更改(如更改應用程序使用的 GC 策略)會產生非常 大的改進。
放大問題階段
EVTK 允許您側重於日志中特定的時間段。當放大特定時間段時,所有摘要數據和建議都會更改以只反 映該時間段。例如,圖 8 中所示的日志顯示白天繁忙但夜晚空閒的應用程序的堆使用情況:
圖 8. 白天繁忙但夜晚空閒的應用程序的堆使用情況
對於整個日志來說,GC 開銷(即花費在執行 GC 上的時間)大約為 5%,這是非常不錯的。但是,這 包括應用程序未執行任何工作以及不需要 GC 時的較長時間段。放大特定時間段可以更精確地反映繁忙期 間系統的行為,如圖 9 所示:
圖 9. 放大繁忙階段
EVTK 還允許您側重於數據的特定范圍。例如,您可能只對非常長的暫停或堆大於 500MB 的階段感興 趣。您可以通過更改 Y 軸的值進行此類篩選。
更改單位
EVTK 允許您更改顯示單位。更改單位將更改繪圖的方式,還會更改摘要表以及調整建議中的單位。
默認情況下,時間(X 軸上的單位)的顯示單位為秒。這對於短時間運行非常方便,但對於涉及較長 時間段的日志不太理想。若要更改為不同的單位,請從右側的下拉菜單中選擇首選的單位,如圖 10 所示 。可能包括小時、分鐘、日期和 GC 編號,該編號只是收集的序號。Normalize 復選框確定是使用相對於 日志(規范)開始的時間還是使用絕對時間(非規范)。
圖 10. 更改單位
您還可以更改 Y 軸上的單位。例如,您可以將堆的數量(默認情況下以兆字節為單位進行顯示)更改 為千兆字節或更改為總堆的百分比。
使用和導出模板
您經常會發現自己查看的是相同的字段組合。在 EVTK 中,模板 允許您保存這些組合以便以後使用。 Templates 視圖位於窗口左上角,如圖 11 所示:
圖 11. Templates 視圖
雙擊模板可將其應用於當前數據集。 EVTK 附帶一些預定義的模板。Heap 模板對於評估應用程序的內 存使用和需求非常有用。Pauses 模板是診斷懷疑可能與 GC 有關的性能問題的第一步。
您可以通過以下方法來導出模板,即調用 View 菜單或通過右鍵單擊 Templates 視圖的任意位置,然 後選擇 Export current settings as template。鍵入模板的名稱,然後,EVTK 將該模板保存在 Templates 視圖中以便將來使用。
更改顏色
您可以選擇 EVTK 用於繪圖的顏色。單擊 View 菜單中的 Preferences 項,然後導航到 Displayers 類別中的 Display colors 頁面,如圖 12 所示:
圖 12. 顏色首選項頁面
保存輸出
您可以保存所有 EVTK 輸出,方法是右鍵單擊主面板,然後從所得到的上下文菜單中選擇 Save,如圖 13 所示。線圖可以另存為 JPEG 圖像,報告可以另存為 HTML,原始數據可以另存為 CSV 文件。本文中 的圖形是從線圖視圖保存的。
圖 13. 保存
使用建議
EVTK 提供詳細 GC 日志中感興趣的功能摘要及其調整建議。摘要和建議可通過 Report 選項卡中的報 告獲得。
為什麼需要進行干預並執行某些手動調整?垃圾收集器為了優化其性能已經執行了很多自動調整。但 是,它不知道您的優先級,也不知道在沒有任何指導的情況下將對哪些方面進行取捨。對於所有工作負載 和所有環境來說沒有最優配置。您可以執行的最簡單的調整就是指定一個策略並告訴垃圾收集器是吞吐量 重要還是暫停時間最重要。 如果您敢於冒險或更渴望獲得最佳性能,則可以嘗試調整堆的大小、改變 nursery 的大小或嘗試更大的最大 nursery 大小。
案例研究:診斷內存洩露
查看詳細 GC 日志的主要原因之一就是檢查應用程序的內存使用情況並確保其在某些方面沒有問題。 例如,應用程序可能使用高於期望的內存,並且詳細 GC 輸出可以提供有關應用程序內存占用的指示。內 存洩露是相關但更嚴重的問題。 Java 平台的 GC 實用工具確保 Java 應用程序不會洩露內存,即使丟失 了對對象的所有引用之前沒有釋放對象也是如此。但是,如果應用程序沒有正確地持有對象引用,則仍然 有可能會洩露,因為垃圾收集器將不會收集仍然被引用的對象。
軟引用和弱引用
軟引用 是在堆中可用空間不足的情況下可以清除的引用。它們對於內存敏感的高速緩存非常有用。弱引 用 是在所引用對象沒有其他引用時將被清除的引用。它們對於將元數據與對象關聯的映射以及維護偵聽 器列表非常重要。當然,如果想永久保留映射中的所有內容,它們不太理想。
通常,診斷內存洩露非常簡單。在應用程序上啟用詳細 GC,將它運行一段時間,然後在 EVTK 中繪制 使用的堆(收集之後)。當應用程序正在初始化或者應用程序的工作負載增加時,應用程序的內存使用自 然也將增加。如果沒有任何明顯的原因應用程序要增加內存要求時,使用的堆的線條向上蔓延,則有可能 存在洩露。EVTK 查找該模式並添加調整建議的注釋(如果檢測到可能存在洩露)。
雖然詳細 GC 可能顯示存在洩露,但它沒有告訴您是哪個對象引起的這個洩露。在某些情況下,通過 檢測代碼才能夠發現。考慮哈希映射和其他集合。它們所有都是靜態的嗎?它們是否都有刪除對象以及添 加對象的機制?應用程序對其緩存對象是否過於慷慨?對象輪詢也可能是內存洩露的原因。
如以下示例所示,弱引用和軟引用都是解決內存洩露的強大工具。考慮確定有洩露的應用程序,如清 單 1 所示。它添加到映射,但從不清除它。
清單 1. 內存嚴重洩露的 Java 類
public class Leaker
{
private Map things = new HashMap();
public void leak() {
while (true) {
things.put(new Date(), new Leak());
}
}
private class Leak
{
private Object data;
public Leak() {
data = new Object();
}
}
}
圖 14 顯示該應用程序的堆使用情況。堆使用中的驟降標記了對堆進行壓縮的點。當 JVM 內存不足時 ,日志結束。
圖 14. 洩露嚴重的應用程序的堆使用情況
使用弱引用來避免洩露
切換到 WeakHashMap,如清單 2 所示,立即更正該問題;新的改進的堆使用情況如圖 15 所示。堆使 用從不會超過 1MB,並且應用程序可以繼續保持不確定地運行。
清單 2. 對防止內存洩露的 Leaker 類的簡單更正
private Map things = new WeakHashMap();
圖 15. 使用 WeakHashMap 修復可能存在洩露的應用程序
但是,弱引用可能不足以更正某些洩露。如果圖 15 中的映射是鏈接的列表,如清單 3 所示,那會發 生什麼情況呢?
清單 3. 對再次引用洩露的 Leaker 類的進一步修改
public class Leaker
{
private Map things = new WeakHashMap();
public void leak() {
Object previousThing = null;
while (true) {
final Leak thing = new Leak(previousThing);
things.put(new Date(), thing);
previousThing = thing;
}
}
private class Leak
{
private Object data;
public Leak(Object thing) {
/* Make a linked list */
data = thing;
}
}
}
弱引用告訴垃圾收集器如果某個對象除了弱引用之外沒有其他任何引用,則應該收集該對象。由於映 射中的每個對象都持有一個對以前的對象的引用,因此將不會清除任何弱引用並且應用程序將快速消耗完 內存,如圖 16 所示:
圖 16. 無法使用 WeakHashMap 修復的內存洩露應用程序中的堆使用
確保弱引用如期工作
通過在 EVTK 中繪制被清除的弱引用的圖,可以證實該問題,如圖 17 所示。向列表中添加鏈接之後 ,被清除的弱引用的數量將從較大的數更改為完全沒有。(修改後的應用程序沿著 X 軸零點的線條非常 短,而以前的應用程序的線條則又長又高。)很明顯,弱引用不再起作用。
圖 17. 對可能存在內存洩露的應用程序的兩個變體清除弱引用
在這種情況下,解決辦法就是也將鏈接列表中的鏈接更改為弱引用。執行如清單 4 中所示的代碼更改 之後,弱應用的數量大大增加了,而且堆使用也返回到最小值:
清單 4. 引進更多弱引用將防止對引用的持有超過必需的時間
private class Leak
{
private WeakReference reference;
public Leak(Object thing) {
this.reference = new WeakReference(thing);
/*
* We can get back our object from the reference with
* reference.get(), but we should always check it for null.
*/
}
}
通過使用 EVTK 查看被清除的弱引用的數量,您可以輕松驗證對使用弱引用的重新設計是否切實有效 。
如果代碼檢測沒有快速找到洩露,則您可能需要執行一些應用程序轉儲並分析它們以找到引用增加了 的對象。
詳細 GC 日志還可以幫助評估應用程序的可伸縮性。例如,如果應用程序的目的是要處理大量數據, 但當測試期間處理少量數據時,該應用程序卻使用非常多的內存,則該應用程序未能按預期進行伸縮。
案例研究:調整堆的大小
很多開發人員都使用詳細 GC 數據來幫助選擇堆的最佳大小。如果堆太小以至於無法容納應用程序所 需的數據,則該應用程序將消耗完內存並且使用 OutOfMemoryError 終止。如果堆具有應用程序數據的空 間,但沒有大量空間備用,則垃圾收集器必須花費很多時間確保堆中具有用於新分配的空間,這將會降低 應用程序的性能。堆太大通常不會對應用程序有負作用,但這樣非常浪費,並且 GC 暫停可能也比較長。 通常,同一機器上會同時運行其他應用程序,因此重新分配內存,使每個 Java 應用程序都具有適量的內 存是非常明智的。
垃圾收集器將嘗試適當調整堆的大小,但將避免使用同一機器上多半的可用物理內存。還需要花費一 段時間來增加堆的大小以便獲得最優大小,如果應用程序占用下降,則可以縮小堆。堆大小的這些上下浮 動可能會降低應用程序的速度,但如果同一系統上運行的其他程序不需要物理內存,則沒有必要這樣。如 果正確理解了應用程序的內存要求,則固定堆大小便是一個簡單的性能優化問題。
對於堆來說,沒有一個理想的堆大小。通常,堆大小越大應用程序執行得越好,因此調整堆大小涉及 權衡應用程序的要求以及物理內存的其他需求。 比較合理的試探是 堆大小應該至少為活動數據的數量的 兩倍。如果這不可能實現或者想使堆更大,則 gencon 策略可能是最佳選擇,因為在堆大小受限制的情況 下,該策略一般勝過 optthruput 策略。如果這些都有可能,則不應該調整堆大小以便機器需要使用虛擬 內存來容納它。使用虛擬內存會大大降低性能。
固定堆大小的優勢
圖 18 顯示了當 圖 7 中的相同工作負載運行在堆大小固定為 500MB 的 JVM 中的暫停時間,命令行 選項為 -Xms500m -Xmx500m。 僅僅根據暫停時間判斷,固定堆大小似乎使問題變得更糟。每個策略的平 均暫停時間都增加了,但花費在 GC 上的時間比例並沒有改變。因此實際上總的暫停時間(報告視圖中表 的第四列)大大下降了。總的暫停時間和平均暫停時間似乎不一致,因為在小堆中收集完成得非常快。當 堆的大小可變時,盡管堆很小,但 JVM 執行很多收集都非常迅速,這些都導致平均暫停時間較短。在這 種情況下,最終的性能度量是 JVM 完成工作所花費的時間(線條的長度);對於 gencon 策略,固定堆 會提高 13%,對於 optavgpause 策略則會提高 15%,而對於 optthruput 策略則會提高 30%。
圖 18. 在固定堆中運行的應用程序的暫停時間
當然,本示例針對的是 Java 程序,它執行時間超過 30 秒,其中 JVM 將初始時間的大部分花費在查 找最佳堆大小上面。固定堆大小通常不會對長時間運行的程序有明顯的改善。如果沒有正確理解工作負載 ,則固定堆大小是非常不明智的,因為這樣可能會迫使 JVM 在太小的堆中運行。可以使用詳細 GC 輸出 來評估工作負載的穩定性以及 JVM 需要高於允許內存的風險。
案例研究:通過詳細 GC 日志評估應用程序吞吐量
您可以調整 GC 以優化應用程序的性能。但是如何確定應用程序執行得如何呢?基准具有明確的性能 度量,但是調整垃圾收集器來優化基准,然後認為相同的配置將會為不同的應用程序提供最佳結果,這是 非常不明智的。所有應用程序都是不相同的,並且垃圾收集器沒有最佳配置。(如果有的話,垃圾收集器 將提供該配置,那麼調整就沒有必要了。) 與基准不同的是,並非所有應用程序都提供顯示其執行好壞 程度的報告。
在這些情況下,詳細 GC 日志本身就可以提供有關應用程序執行情況的最佳提示。盡管詳細 GC 日志 是評估應用程序性能的好起點,而報告的暫停時間一定不是 正確的起點。正如上文所示的固定堆示例, 應用程序可能有時的運行速度比調整前快,但仍然由未改變的 GC 開銷甚至更長的平均暫停時間表示。花 費大量時間執行 GC 的應用程序的確看上去性能降低了,但由於對象的排列方式,在 GC 上花費更多時間 有時可能會使應用程序執行得更好。
GC 如何加快應用程序的速度?
好的 GC 實際上會提高應用程序的性能。如果對象布置緊密,則當它們使用 gencon 模式時或緊縮之 後,分配新對象將更迅速,因為不需要搜索空閒列表。這在詳細 GC 日志中度量不出來,但快速分配大大 有助於應用程序。如果對象布置適當,以便在相同時間使用的對象不會彼此靠近(這稱為區域性),則對 象訪問也將比以前更快。智能垃圾收集器重新排列對象以便最大程度地提高訪問對象的速度。
這裡沒有計算 GC 暫停時間對應用程序的影響,而是查看生成的垃圾數量。應用程序性能的一個最佳 指標是應用程序生成的垃圾的數量:生成的垃圾越多,則應用程序必須執行的工作也就越多,原因垃圾是 應用程序工作的負作用。由於收集了所有生成的垃圾,因此生成垃圾的數量與垃圾收集器收集的垃圾數量 完全相同。
您可以繪制收集的垃圾數量的圖形,方法是從 EVTK 的 VGC Data 菜單中選擇 Amount freed。Report 選項卡上的圖形 顯示了有關在運行期間所收集的平均垃圾數量和總垃圾數量的統計信息。 平均釋放數量 並不能表示最佳性能;如果占用非常穩定,則每次收集釋放的數量也可能非常穩定。但是,如果應用程序 執行得非常好,則當應用程序在更短的時間內執行更多的工作,收集的頻率可能會隨之增加。因此,在固 定的時間段內,釋放的總數量可以很好地表示性能。如果在固定的時間段內沒有收集日志,則放大該設置 的時間段將確保只顯示該時間段的總數量。
更好的性能指示器是 GC 的速率,因為即使您對不具備相同時間的日志進行比較,它仍然非常有意義 。該速率顯示在 Report 選項卡頂部的表中。(如果未顯示表,則嘗試在 VGC Data 菜單中啟用 Summary 。)讓 GC 擁有較高的速率意味著應用程序在較短的時間內做完更多的工作 — 這是好事!
考慮前面固定堆的示例。平均暫停時間造成了有關 GC 執行性能的假象。但是,如果您看一看 GC 的 速率,便可以看出對於固定的堆來說這是較高的速率。例如,兩個 optthruput 運行的比較,如圖 19 所 示,其速率比提前設置堆大小時的速率高出 12%:
圖 19. 垃圾收集速率的摘要視圖
您還可以將 GC 的速率看作是垃圾生成的速率。乍看起來,垃圾生成似乎是一件壞事,並且應該盡量 減少為好。生成垃圾多的應用程序可能比生成垃圾少的應用程序性能更差,這是正確的。因為應用程序花 費太多精力在垃圾收集器上 — 但並不總是這樣。例如,對象輪詢會減少應用程序生成的垃圾數量,但嚴 重降低了垃圾收集的性能。通常,持有可以丟棄的對象引用會減少生成的垃圾數量,但是這樣會損害 GC 。如果您適當地限制了變量的范圍並且減少實例變量的使用,則可以減少此類對象保留。
如果應用程序負載不足 — 即它沒有足夠的工作可做 —, 則 GC 的速率不可作為良好的性能指示器 ,因為如果沒有工作可做,速率將會下降。例如,如果所有客戶機都斷開連接,服務器將不會生成大量垃 圾,但是這並不意味著需要調整服務器。總之,當應用程序工作負載不足時,可能不需要進行大量調整。 如果目標是加快各個事務的速度,則在某段事務上放大 GC 日志將會提供合適的信息。
估計應用程序響應時間
與應用程序吞吐量相比,如果您更關心應用程序響應時間又會怎樣呢?那麼假設詳細 GC 暫停時間是 應用程序響應時間的最佳指示器。但這只在某些時候是正確的,並且只有一半的正確性,因此您需要對暫 停時間仔細推斷。如果應用程序工作負載不足,則最大暫停時間將與最大響應時間有關。但是,通常平均 響應時間與吞吐量成正比。 因此,與具有短暫停時間的策略(如 optavgpause)相比,具有較長暫停時 間的策略(如 optthruput)實際上可能給出更低 的平均響應時間。如果應用程序工作負載過重,則暫停 時間甚至不太重要,因為工作可能必須排成隊列進行服務,因此響應時間可能很大程度上由隊列的長度確 定,而隊列的長度又由應用程序吞吐量確定。
結束語
如果仔細查看詳細 GC 日志,通常會更好地了解應用程序的特性;還將能夠檢測應用程序內存使用的 潛在嚴重問題並且能夠提高性能。 Extensible Verbose Toolkit 是一個功能強大的工具,可以從詳細的 GC 中獲得最多的可用信息。