用 verbose GC 分析 IBM WebSphere Portal 的記憶體問題

CloudSpace發表於2010-09-21
Matt Munse, 支援工程師, IBM

簡介: 瞭解 verbose GC(垃圾收集,garbage collection)以及如何使用這個工具來分析 IBM® WebSphere® Portal 伺服器的記憶體問題。本文討論了 JVM 的生命週期,向您展示瞭如何為 Java SDK 1.4.2 執行額外的調優。

垃圾收集簡介

本文針對的是 WebSphere Portal 版本 6.0 或更高版本(和 5.1 或更高版本),其上執行的是 Java™ Software Development Kit (SDK) 1.4.2。為了獲得最佳調優,建議使用 SDK 的最新服務釋出,對於這裡所討論的調優,至少使用一個新於 SR13 的服務釋出。

垃圾收集可簡單定義為 JavaTM Virtual Machine (JVM) 釋放不再被程式引用或使用的物件的 heap 的行為,heap 是指記憶體中的一個預先定義的部分,可用來管理 Java 應用程式內的資源。

這個過程有三個主要階段:mark、sweep 和 compact:

  • 在 mark 階段,heap 內的所有物件均以一個 bit “標記”。然後檢查這些物件看它們是否仍被引用,如果如此,這個 bit 即被刪除。
  • 在 sweep 階段,JVM 遍歷整個 heap 並刪除仍具有標記 bit 的所有物件;這些物件不再被引用或使用。
  • compact 階段只在一個完整的 GC 中才會執行,在這個階段,GC 會試圖將 heap 內的所有物件重新分配到 heap 內的一個更為緊縮、更為連續的較小空間。

監視 heap 使用情況的最好的方法是分析這個 verbose GC 的輸出。

首先要確保該 verbose GC 在伺服器上已被啟用:

  1. 從 IBM WebSphere Application Server 管理控制檯,導航到 Application Servers - WebSphere_Portal - Java and Process Management - Process Definition - Java Virtual Machine。
  2. 確保選中 Verbose garbage collection 旁的核取方塊,然後重啟此伺服器。
  3. 現在,應該可以看到類似於如下的條目被寫入到 native_stderr.log 檔案:




    = 32), weak 11, final 7424, phantom 0>

分析這個日誌檔案條目

現在,讓我們將之前的日誌條目細分成幾個部分並分別加以分析。

首先是:

通過這個條目,我們能夠知道有一個分配失敗,當 heap 內沒有足夠的連續空間可以分配給物件時,就會發生分配失敗。物件是 verbose GC 輸出中最為常見的。在本例中,它是一個 528 位元組的小物件。

從此行可以看出,自我們上次執行了一個 GC 迴圈後已經過去了一段時間,3602594 ms。

接下來,我們研究最後一行:

此行告訴我們在 GC 上花費的時間的數量。使用這個數字,我們就能夠獲得我們最近一次用在 GC 內的比率並找出我們花在 GC 和非實際工作上的時間比例;比如:

460/3602594 = .000127% of the time was spent in GC

在一個健康的伺服器內,花在 GC 內的時間應該少於 13%,理想的是 7-10% 左右。

回到第二行:

首先注意到的一點是 “action=”。對於一個分配失敗,有七種不同的動作可以發生:

  • action=0 表示 pinnedFreeList 已用盡。
  • action=1 表示進行的是一個搶佔式的垃圾收集迴圈。
  • action=2 表示一個完全的分配失敗。
  • action=3 表示發生了一個 heap 擴充。
  • action=4 表示所有已知的軟引用均已清除。
  • action=5 表示對臨時 heap 進行了臨時偷用。
  • action=6 表示空閒空間非常低。

這些動作的順序代表了嚴重程度的等級;最嚴重的情況是伺服器的記憶體完全用完(action=6)。

在本例中,我們用的是 action=1。在這類 GC 內,只執行 GC 的 mark 和 sweep 階段。

此行還包括了:

(0/585421800) (29966688/30811672)

此行特定於 AIX® JVM。正如您看到的,這裡有兩個基礎數字,如果二者加起來,就表示 heap 被擴充套件到的大小。其中一個較小,會被自動用於大型物件的分配。

注意:大型物件指的是大於 64 K 的物件。

所有其他物件均被放在 heap 的其他部分。如果大型物件部分已滿且有另一個大型物件請求分配,那麼這個物件就被放在 heap 的另一個主要部分上。這個物件可以是其他多平臺 JVM 內的一個有用的調優引數,稱為 -Xloratio。

下一行是:

此行報告了 GC 迴圈開始的時間以及我們所處的是哪個迴圈,即 GC(177)。這是自 JVM 開啟以來執行的第 177 個 GC。

隨後的一行:

表明在此 GC 迴圈期間,有多少位元組被釋放,在本例中,JVM 能夠釋放 218620376 位元組。

並且,我們還能看出這個 heap 現在有 40% 是空閒的,即在這個 heap 總共 616,233,472 位元組中有 248,587,064 位元組是空閒的:

(248587064/616233472)

最後,這個 GC 迴圈的完成花了 459 ms。

這一行非常有用,因為它告訴我們這個 heap 有多少是空閒的,而且有一些物件正在被清理且不會佔用這個 heap。

接下來的一行是:

這一行對於我們稍候為了使 GC 執行得更快而進行的 GC 配置調優非常有用(參見 -Xgcpolicy:optavgpause),但現在,我們只需知道:

這一行顯示了每個迴圈執行的時長。mark 階段 422 ms,sweep 階段 37 ms,compact 階段 0 ms。

此外,通過如下兩點,我們可以知道這不是一個完整的 GC:

  • compact 階段的完成花了 0 ms。
  • action=1 表明這是在完整 GC 執行之前的一個搶佔式的 GC。

最後要研究的一行是:

= 32), weak 11, final 7424, phantom 0>

我們必須首先理解這個 GC 不僅管理物件,而且還會維護對實際物件的一個單獨的引用物件。這些引用與建立時的四個查詢中的一個相關,並且這種相關性在日後不能更改。這四個查詢在 mark 階段按如下順序標記:

  1. Soft
  2. Weak
  3. Final
  4. Phantom

Soft 和 weak 引用在引用不復存在時可被清除。如果一個 finalizer(Final 查詢)與一個 soft 或 weak 引用相關,那麼只有當這個 soft 或 weak 引用刪除後,這個 finalizer 才會在所執行的下一個 GC pass 上被刪除。

以上所述就是在 Java SDK 1.4.2 上的預設 GC 策略內能看到的一些主要行。有了這些基礎,讓我們接下來探索 JVM 是如何工作的。

JVM 的開始部分一般是一些用來啟動程式的可選命令列引數。這些引數是新增到 WebSphere Application Server 管理控制檯內的原生 JVM 引數。

如下是一個基本命令,它會在 Java 程式開始時執行:

Java –Xmx1024M –Xms256M –Xverbosegc programToRun

其中的命令列引數

–Xmx1024M –Xms256M –Xverbosegc

表明啟動時如下事情會發生:

  • heap 最大為 1024 M (–Xmx1024M)
  • heap 最小為 256 M (–Xms256M)
  • 使用了 Verbose GC (–Xverbosegc)

有一點非常重要,那就是即便我們為 JVM 分配了 1024 M,正如我們在上述的 verbose GC 片段內所做的那樣,但這並不意味著我們一定會使用或我們永遠不會使用這全部的 1024 M。例如,在這個 verbose 片段,我們只使用了 616,233,472 位元組。

JVM 先是被分配最少的 256 M 並將該空間付諸使用。如圖 1 所示,整個 heap 的 1024 M 被放置起來備用(整個條),只有最初的 256 M 被分配使用,如圖中陰影區域所示。


圖 1. heap 的示意圖
heap 的示意圖

由於只有 256 M 可用,因此 JVM 會先用執行程式 programToRun 所需的這些物件填充這最初的 256 M。這些物件會被逐漸新增到這個空間,直到沒有足夠的連續空間能滿足將下一個物件放在 heap 上的要求(參見圖 2)。


圖 2. 載入了不同大小的物件後的 Heap
載入了不同大小的物件後的 Heap

這時,下一個物件開始請求這個 heap 上的空間,但已經沒有足夠的連續空間能夠滿足這一請求(參見圖 3)。


圖 3. 請求物件的示意圖
請求物件的示意圖

這個請求發生時,JVM 以 action=1 執行一個 GC,示意圖會發生變化,如圖 4 所示。


圖 4. JVM 以 action=1 執行 GC (請注意未用物件已被刪除)
JVM 以 action=1 執行 GC (請注意未用物件已被刪除)

成為:


圖 5

因此,此時該請求可被滿足:


圖 6

有時,物件可能無法被移動或清除;這些物件一般是類物件和仍在使用的物件。假設,這時有兩個與之前的那個請求類似的請求進來,但是 heap 上卻再沒有物件可被刪除(參見圖 5)。


圖 5. 使用中的 heap 的當前表示
使用中的 heap 的當前表示

在本例中,這個 GC 會執行一個 action=2,以試圖執行一個 compaction 階段。在 compaction 階段,heap 上的物件會被整合以便將所有空閒空間收集起來滿足當前的請求。

heap 的示意圖這時應該類似圖 6。


圖 6. 緊縮了的 heap
緊縮了的 heap

這時,下一個請求就可以被處理了(參見圖 7)。


圖 7. heap 的進展
heap 的進展

瞭解了 GC 動作 1 和 2 之後,我們現在可以來看看即便執行了動作 1 和 2 後仍沒有足夠空間可以分配給下一個物件時,該如何做。

如果我們繼續處理我們的 heap 時,就會發生這種空間缺乏的情況,因為我們已經假設並非所有物件都能被清除(參見圖 8)。


圖 8. 緊縮了的 heap 且分配空間已滿
緊縮了的 heap 且分配空間已滿

可以看出,我們所使用的這個 heap 的可用空間已滿。在我們的例子中,我們的這個 heap 的 256 M 已滿,因為我們只為 heap 分配了 256 M 作為可用空間。請務必記住我們的 heap 的最大可達 1024 M,所以如果發生了這種情況,我們還有更多的空間可用。

在本例中,JVM 執行了一個 action=3 並將這個 heap 擴充套件了 65,535 位元組以便提供更多的 heap 供我們使用(參見圖 9),在 verbose GC 中可以看到這種情況。


圖 9. 擴充套件了 65536 位元組的系統 heap
擴充套件了 65536 位元組的系統 heap

成為這個:


圖 12

現在,處理可以繼續。GC 迴圈會繼續,直到所有新分配的 heap 均經歷上述相同的動作後,再分配更多的 heap,直到最後全部 1024 M 分配完畢。

從這種方法,我們可以看出,從一個較小的初始 heap 開始,再不斷增加 heap 會使 heap 保持緊密和相對健康。

如下所示是來自完整 GC 的一個 verbose GC 的例子,後跟一個 heap 擴充套件:



  
  
  = 32), weak 0, final 102, phantom 0>

  
  
  
  
  
  
  


理解了 heap 如何增長以及如何為 JVM 所用之後,現在您就能更好地理解程式執行時有多少記憶體被 JVM 實際使用。

對於一個程式或 portal 在執行時使用多少記憶體,存在著一些誤解。通常,portal 的使用者通過 ps avg 命令或 Microsoft® Windows® Task Manager(它顯示了系統分配給 Java 程式的記憶體數量)來決定 Java 程式使用了多少記憶體。

如您所見,分配給程式 X 數量記憶體,並不意味著有程式實際使用了這麼多數量的記憶體。

如果 Task Manager 或 “ps avg” 顯示 Java 程式使用了 616 M 記憶體,但如果我們檢視這個 verbose GC,就會看到它當前只使用了該數量的 40%:

在 heap 內仍有空閒記憶體時出現 OUTOFMEMORY 錯誤

如果在 heap 內仍有空閒記憶體的情況下收到一個 OUTOFMEMORY 錯誤,可以通過檢視此 verbose GC 來判斷是何原因。如下所示的是來自 verbose GC 內的一個記憶體不足錯誤的示例輸出:



  
  
  = 32), weak 0, final 6, phantom 0>
  

  
  
  
  
  
  
  


  
  
  = 32), weak 1178, final 70, phantom 0>
  

JVMDG217: Dump Handler is Processing OutOfMemory - Please Wait.
JVMDG315: JVM Requesting Heap dump file
JVMDG318: Heap dump file written to
/opt/WebSphere/AppServer/heapdump.20090716.112135.19377.phd
JVMDG303: JVM Requesting Java core file
JVMDG304: Java core file written to
/opt/WebSphere/AppServer/javacore.20090716.112144.19377.txt
JVMDG274: Dump Handler has Processed OutOfMemory.



如您所見,在這個例項中有足夠的空閒記憶體:

但要求分配空間的物件也相當大:

有兩種方式可以應對這類記憶體不足的情況:一個方式是使用 k-cluster (-Xk),另一種方式是為較大的物件定義一個 heap 段(–Xloratio),其中 loratio 意思是 large object ratio。

-Xkn

此引數為要儲存的類物件在 heap 內留出了一個空間。正如在之前的例子中看到的,使用中的引用不能被移動或清除。因此它們被 “釘” 在了 heap 內的這個位置,如果有足夠多的物件被釘在了 heap 內的不同位置,那麼這個 heap 就會變得破碎。

為了解決這種分裂問題,可以使用引數 –Xkn,它為類物件專門提供了 heap 的一個段。n 的一個比較好的開始值是引數 40,000,即便需要使用 WebSphere Portal Support 進行系統調優分析。

如果我們將這個引數設為 40,000,JVM 就會允許在這個 k-cluster 中最多儲存 40,000 個類物件。如果這個 k-cluster 已滿,系統會正常繼續並會使用 heap 的其餘部分進行處理。

為了在管理控制檯內將 –Xk 新增給原生的 JVM 引數,需遵循如下步驟:

  1. 在 WebSphere Application Server V6.0 上,選擇 Servers - Application Servers - server_name - Java and Process Management - Process definition - Java Virtual Machine - Generic JVM Arguments。

    在 WebSphere Application Server V5.0 and V5.1 上,選擇 Servers - Application Servers - server_name - Process Definition - Java Virtual Machine - Generic JVM Arguments。
  2. 輸入 –Xk40000,儲存此配置,然後重啟伺服器。

–Xloration

此設定允許將 heap 的一個部分留出來供大型物件使用,這裡的大型物件指的是大於 64 K 的物件。

正如我們在之前的示意圖中看到的,需要為物件分配 heap 上的連續空間。很顯然,尋找 528 位元組的連續空間要比尋找大於 1,000,000 位元組的連續空間簡單很多。

如果大型物件均被存於 heap 的特定段內,那麼它們可更容易地被分配;並且,較小的物件無需與它們爭奪 heap 使用。

通過在 verbose GC 實現後監視這個 GC 可以調優這個引數的值。此引數的值 n,代表的是此時總的 heap 被擴充套件的百分比。對於本例,我們使用了值 –Xloratio0.2。

當此值被新增到伺服器後,可以看到在 verbose GC 輸出中多了一個新行,如下所示:

如下所示的是來自啟用了 –Xloratio0.2 的 verbose GC 的一個塊:



  
  
  
  = 32), weak 0, final 0, phantom 0>


其中的一行(如下所示)表示我們如何能調優這個值 n,因為我們現在可以看到 heap 的佈局了:

第一個比率是 heap 的常規區域(80% 用於常規處理),第二個比率是我們為大型物件分配的 heap 段(20% 分配給大型物件)。

此外還顯示了每個部分內的空閒空間數量。從這個輸出我們可以看出,整個大型物件段均空閒,表明沒有大型物件在使用。

如果這種趨勢繼續,我們需要將大型物件的 heap 段從 20% 減低到 10%,以留出更多空間給常規處理使用。我們使用 –Xloratio0.1 作為新值,最好以 10% 或 0.1 作為開始值。

為了在管理控制檯內將 –Xloratio 新增到原生 JVM 引數,需遵循如下步驟:

  1. 在 WebSphere Application Server V6.0 上,選擇 Servers - Application Servers - server_name - Java and Process Management - Process definition - Java Virtual Machine - Generic JVM Arguments。

    在 WebSphere Application Server V5.0 and V5.1 上,選擇 Servers - Application Servers - server_name - Process Definition - Java Virtual Machine - Generic JVM Arguments。
  2. 輸入 –Xloratio0.1,儲存此配置,然後重啟此伺服器。

多處理器

可以使用多個執行緒來執行 GC。這個數值應比系統上有的處理器的數量少 1;比如,如果系統上有四個處理器,那麼為 GC 應該使用三個執行緒。要獲得這個值,我們採用瞭如下引數:

–Xgcthreadsn
為了在一個四處理器系統上設定三個執行緒,需在管理控制檯內將 –Xgcthreads 引數新增到原生 JVM 引數,遵循如下步驟:

  1. 在 WebSphere Application Server V6.0 上,選擇 Servers - Application Servers - server_name - Java and Process Management - Process definition - Java Virtual Machine - Generic JVM Arguments。

    在 WebSphere Application Server V5.0 and V5.1 上,選擇 Servers - Application Servers - server_name - Process Definition - Java Virtual Machine - Generic JVM Arguments。
  2. 輸入 –Xgcthreads3,儲存此配置,然後重啟伺服器。

Post SR12

能改善 GC 效能的另一個方法是啟用併發 mark 引數。正如從 verbose GC 的輸出中能夠看出的,GC 迴圈的 mark 階段花費了大多數時間。比如:



  
  
  
  = 32), weak 19, final 582, phantom 3>


這裡,總共 685ms 的時間中,607ms 被花費在 mark 階段。為了縮短所花費的時間,可以引入如下引數,該引數被證明可以在版本 SR13 或 JDK 的更新版本中工作得很好:

–Xgcpolicy:optavgpause
此引數縮短了花在 mark 上的時間並在 GC 迴圈未在進行中時仍能保持 mark 階段執行。

為了將 –Xgcpolicy:optavgpause 引數新增到管理控制檯(SR13 或 Java SDK 1.4.2 的更新版本)內的原生 JVM 引數,遵循如下步驟:

  1. 在 WebSphere Application Server V6.0 上,選擇 Servers - Application Servers - server_name - Java and Process Management - Process definition - Java Virtual Machine - Generic JVM Arguments。

    在 WebSphere Application Server V5.0 and V5.1 上,選擇 Servers - Application Servers - server_name - Process Definition - Java Virtual Machine - Generic JVM Arguments。
  2. 輸入 –Xgcpolicy:optavgpause,儲存此配置,然後重啟此伺服器。

至此,您應該已經瞭解了 verbose GC 以及如何使用此工具來分析和解決 WebSphere Portal 伺服器記憶體問題。

原文連結:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1003_munse/1003_munse.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14789789/viewspace-674569/,如需轉載,請註明出處,否則將追究法律責任。

相關文章