問題描述
系統管理員或用戶注意到 JAVA 進程消耗大量的 CPU 資源,
並想要了解是哪個方面消耗了大量 CPU 資源,以及導致出現這種現象的原因。
故障排除
請注意,並非下面所有任務都需要完成。有些問題僅通過執行幾項任務就可以解決。
爲什麼發生此問題?
發生此問題有許多原因:JAVA創建的線程、不良編碼習慣或第三方軟件。
遺憾的是,證明在什麼地方發生此問題有時候非常困難。
本模式嘗試通過利用特定操作命令和收集數據來幫助排除此問題。
收集高 CPU 佔用率的數據
對於有關收集高 CPU 佔用率的數據的特定操作信息,請根據您的操作系統執行以下步驟。
重要說明:
這些操作系統的所有信息都基於 Sun JVM。
目前在 JRockit 中還沒有辦法將 PID 從說明 CPU 佔用率的操作系統命令(prstat、top、pslist 等等) 映射到 Thread Dump 中的正確線程。 從 Jrockit 的 70SP4RP2 和 81SP2RP1 以後的版本起,就可實現此映射。
例如,在 Linux 中,Thread Dump 在以後的版本中將採用如下形式(PID 顯示在 Thread Dump 中):
=====================================================================
"ExecuteThread: '20' for queue: 'default'" id: 0x00000e80 prio: 5 ACTIVE, DAEMON, GCABLE
thread: 0x469b0af0 lastj: 0xac0f19c
pt_thr: 237596 pid: 23166
at COM.jrockit.vm.Classes.defineClass0(Native Method)@0x8b4b798
at COM.jrockit.vm.Classes.defineClass(Unknown Source)@0x8b4b8b1
at java.lang.ClassLoader.defineClass(Unknown Source)@0x8b4b46f
=====================================================================
在上例中,PID 是 23166,您可以通過 Linux 或任何所在系統上的 top(或任何您需要在操作系統上使用的特定命令)輸出直接關聯該 PID。
轉換爲十六進制號碼
備註:爲協助您計算在本模式中討論的十六進制值,您可以在 Shell 腳本中使用下列行將十進制號碼轉換爲十六進制號碼。
如果您使用 Unix 操作系統,那麼轉換會很方便。
dec2hex.sh:
printf "dec -> hex: %d = %x /n" ${1} ${1}
用法:
$ sh dec2hex.sh 755
dec -> hex: 755 = 2f3
JAVA Thread Dump 方法:(kill -3)
在分析問題時需要使用 kill –3 PID 生成 javacore 和 heapdump 文件
但有時候僅javacore文件生成了,而heapdmp文件卻沒有生成。
就是因爲在啓動這個JAVA的時候,沒有在環境變量中設置相應的參數:
export JAVA_DUMP_OPTS="ONANYSIGNAL(JAVADUMP[5],HEAPDUMP[5])"
或者
export JAVA_DUMP_OPTS="ONDUMP(JAVADUMP[15],HEAPDUMP[5]), ONOUTOFMEMORY(JAVADUMP[15]],HEAPDUMP[5])"
如果是IBM WebSphere Application server instance,還可以使用 export IBM_HEAPDUMP=true
而 IBM_HEAPDUMPDIR 這個參數可以設置WebSphere Application Server生成的heapdump的位置
具體這個如何設置這個參數可以參考:
http://publib.boulder.ibm.com/infocenter/javasdk/v1r4m2/index.jsp (for JAVA 1.4.2)
http://publib.boulder.ibm.com/infocenter/javasdk/v5r0/index.jsp (for JAVA 5)
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
LINUX
>>>>>>>>>>>>>>>>
1.獲得最頂端輸出並查找與之前啓動了現佔用 CPU 的 WLS 的那個用戶 ID 相關聯的 PID。
2.通過 kill -3 <PID> 對 WebLogic Server 進行若干 Thread Dump
3.將步驟 1 中的 PID 號轉換爲一個十六進制值。
(用於 Linux 的 JVM 將 Java 線程作爲本地線程實現,這使每個線程成爲一個獨立的 Linux 進程。)
4.在 Thread Dump 中搜索 nid 的值等於上一步驟中所得到的十六進制值的線程。
這將爲您揭示造成高 CPU 佔用率問題的線程。
下面是 Linux 系統中上述進程的一個示例:
1.獲得 top輸出並查找與之前啓動了現佔用 CPU 的 WLS 的那個用戶 ID 相關聯的 PID。
2.將該號轉換爲一個十六進制值。
請參閱下面的 top 輸出示例(這只是一個代碼片斷,因爲對於單個 WLS 進程將啓動更多的線程)。
在 Linux 中,每個線程映射到一個不同於其它 Unix 形式的進程中(每個線程都有自己的 PID)
=====================================================================
PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND
...........
22962 usera 9 0 86616 84M 26780 S 0.0 4.2 0:00 java
...........
=====================================================================
備註:ps 和 top 只顯示主(初始)線程。要查看所有線程,使用 ps -m 命令或在 top 中鍵入[Shift] -[H] 組合鍵。
如果 PID 爲 22962,則十六進制值將是:0x59B2
3.使用此十六進制值並在 Thread Dump 中查找哪個 nid 等於該值,以便從 Thread Dump 中獲取正確的線程。
例如,如果 ExecuteThread 0 出現問題,則 0x59B2 將對應於該線程:
===========================================================================
"ExecuteThread: '0' for queue: 'default'" daemon prio=1 tid=0x83da550 nid=0x59b2 waiting on monitor [0x56138000..0x56138870]
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:415)
at weblogic.kernel.ExecuteThread.waitForRequest(ExecuteThread.java:146)
at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:172)
======================================================================
4.然後,您可以檢查該線程以確定它正在執行的任務以及是否出現問題。
在上述示例中,由於該線程此時佔用 0% 的 CPU,所以只顯示執行此操作的進程。
理想狀態下,應當迅速並且連續完成全部三個步驟,以便儘可能及時地捕捉數據。
這可以通過類似下面的一個簡單的 shell 腳本來完成。
======================================================================
#
# Takes an argument (PID of the WLS process) and loops three times.
# This will append the prstat information to a file called dump_high_cpu.txt.
# The thread dump information will either be in file where stdout was redirected or printed on the screen.
#
for loopnum in 1 2 3
do
top -b -n1>> dump_high_cpu.txt
kill -3 $1
echo "cpu snapshot and thread dump done. #" $loopnum
sleep 1
echo "Done sleeping."
done
============================================================================
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
SOLARIS
>>>>>>>>>>>>>>>>
1.在 Java 進程中運行 prstat命令。重複幾次這個操作,以便您能夠看到一種模式。例如:prstat -L -p <PID> 1 1
2.在 Java 進程中運行 pstack命令以獲得從輕量型進程 (LWP) 到 PID(進程 ID)的映射。
示例:pstack 9499 並將輸出結果重定向到一個文件。
如果您使用 Solaris 中的常規線程庫(即,在 LD_LIBRARY_PATH 中沒有 /usr/lib/lwp),
LWP 就不會直接映射到操作系統線程,因此您必須從進程中執行 pstack(所以檢查看您是否正在使用替代線程庫)。
3.經過一段時間後對服務器進行若干 Thread Dump,確保您執行正確的線程。
您可以通過在 Java 進程中執行 kill -3 <PID>來達到此目的。
4.將 LWP ID 映射到 Java 線程 ID。
例如,如果上述的 LWP 爲“8”,它可以映射到 Java 線程“76”。然後將 76 換算爲十六進制值 0x4c。
5.檢查 Thread Dump,找到匹配“nid= <上述標識符/值>”的線程。
在本示例中,您找到匹配“nid=0x4c”的線程,而該線程就是正在消耗 CPU 資源的那個線程。
下面是 Solaris 系統中上述進程的一個示例:
1. 在 Java 進程中運行 prstat 命令。
======================================================================
$ prstat -L -p 9499 1 1
PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/LWPID
9499 usera 153M 100M sleep 58 0 0:00.22 0.6% java/8
9499 usera 153M 100M sleep 58 0 0:00.10 0.2% java/10
9499 usera 153M 100M sleep 58 0 0:00.11 0.1% java/9
9499 usera 153M 100M sleep 58 0 0:00.03 0.0% java/5
9499 usera 153M 100M sleep 58 0 0:01.01 0.0% java/1
9499 usera 153M 100M sleep 58 0 0:00.00 0.0% java/12
9499 usera 153M 100M sleep 58 0 0:00.00 0.0% java/11
9499 usera 153M 100M sleep 58 0 0:00.00 0.0% java/14
9499 usera 153M 100M sleep 58 0 0:00.00 0.0% java/13
9499 usera 153M 100M sleep 59 0 0:00.07 0.0% java/7
9499 usera 153M 100M sleep 59 0 0:00.00 0.0% java/6
9499 usera 153M 100M sleep 59 0 0:00.00 0.0% java/4
9499 usera 153M 100M sleep 58 0 0:00.11 0.0% java/3
9499 usera 153M 100M sleep 58 0 0:00.00 0.0% java/2
====================================================================
2.運行 pstack 命令。
示例:pstack 9499 並將輸出結果重定向到一個文件。
如果您使用 Solaris 中的常規線程庫(即,在 LD_LIBRARY_PATH 中沒有 /usr/lib/lwp),
LWP 就不會直接映射到操作系統線程,因此您必須從進程中執行 pstack(所以檢查看您是否正在使用替代線程庫)。
上述示例顯示“java/8”進程在 prstat 的頂端。
3.爲“lwp# 8”檢驗 pstack輸出結果。
您會發現“lwp# 8”從 pstack 輸出結果映射到“thread# 76”,如下所示。
=================================================================
----------------- lwp# 8 / thread# 76 --------------------
ff29d190 poll (e2e81548, 0, bb8)
ff24d154 select (0, 0, 0, e2e81548, ff2bf1b4, e2e81548) + 348
ff36b134 select (0, bb8, 7fffffff, fe4c8000, 0, bb8) + 34
fe0f62e4 __1cCosFsleep6FpnGThread_xl_i_ (0, bb8, fe4c8000, 1, 0, 1e2fd8) + 234
fe23f050 JVM_Sleep (2, 0, bb8, fe4de978, fe4c8000, 1e2fd8) + 22c
0008f7ac ???????? (e2e818d4, bb8, 1e2fd8, 984a4, 0, 109a0)
0008c914 ???????? (e2e8194c, 1, fe4d6a80, 98564, 8, e2e81868)
fe5324e8 __1cMStubRoutinesG_code1_ (e2e819d8, e2e81c10, a, f6cb5000, 4, e2e818f0) + 3ec
fe0cbe94 __1cJJavaCallsLcall_helper6FpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v_ (e2e81c08,fe4c8000, e2e81b54, 1e2fd8, 8e764, e2e81c10) +308
fe1f6dbc __1cJJavaCallsMcall_virtual6FpnJJavaValue_nLKlassHandle_nMsymbolHandlee81c08, e2e81b54) + 150pnGThread__v_(f6cb64b8, e2e81b40, e2e81b44, fe4c8000, e2d8) + 60e_5pnGThread__v_ (e2e81c08, e2e81c04, e2e81c00,e2e81bf4, e2e81bec, 1e2f8000, e2e81d10, 1e, e) + 120FpnKJavaThread_pnGThread__v_ (f6817ff8, 1e2fd8, fe4c
7fd70) + 3d8cKJavaThreadDrun6M_v_ (e2e02000, fe4d3e34, fe4c8000, 7fd70, 1e2fd8,
fe213ec8 _start (fe4c8000, fe625d10, 0, 5, 1, fe401000) + 20
ff36b728 _thread_start (1e2fd8, 0, 0, 0, 0, 0) + 40
======================================================================
4.通過在 Java 進程中執行以下命令對服務器進行 Thread Dump:
kill -3 <PID>。
5.由於 lwp# 8 映射到 thread #76,您可以將 76 換算爲十六進制值 4c。
該值映射到 JVM Thread Dump 中的 nid=0x4c:
================================================================
"Thread-6" prio=5 tid=0x1e2fd8 nid=0x4c waiting on monitor [0xe2e81000..0xe2e819d8]
at java.lang.Thread.sleep(Native Method)
at weblogic.management.deploy.GenericAppPoller.run(GenericAppPoller.java:139)
=======================================================================
在此示例中,佔用最多 CPU 資源的線程實際上正處於休眠狀態。應用程序輪詢程序在開發模式啓動的服務器上運行。
由於它每隔 30 秒運行一次,因此顯然無法及時捕捉 Thread Dump 以瞭解此線程中的活動。
理想狀態下,應當迅速並且連續完成全部三個步驟,以便儘可能及時地捕捉數據。這可以通過類似下面的一個簡單的 shell 腳本來完成。
=========================================================================
#
# Takes an argument (PID of the WLS process) and loops three times.
# This will append the prstat information to a file called dump_high_cpu.txt.
# The thread dump information will either be in file where stdout was redirected or printed on the screen.
#
for loopnum in 1 2 3
do
prstat -L -p $1 1 1 >> dump_high_cpu.txt
pstack $1 >> dump_high_cpu.txt
kill -3 $1
echo "prstat, pstack, and thread dump done. #" $loopnum
sleep 1
echo "Done sleeping."
done
=====================================================================
6.然後,您可以檢查該線程以確定它正在執行的任務以及是否出現問題。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
AIX
>>>>>>>>>>>>>>>>
1. 執行: ps -mp <PID> -o THREAD 以查找正在佔用 CPU 的 tid。
您應當查看“CP”列(表示 CPU 佔用率),看其中哪些線程的此項值比較高並從中挑選一個線程。
2. 通過執行以下命令對服務器進行 Thread Dump:
kill -3 <PID>
3. 運行: dbx -a <PID>
4. 在 dbx 中時,運行 dbx thread命令(以列出所有線程)。
5. 查找與您通過 ps -mp <PID -o THREAD 命令獲取的 TID 匹配的行。
該行中的號碼應當採用“$t<NUM>”格式,其中“NUM”是一個號碼。
6. 在 dbx 中時,運行 dbx 命令 th info <TID>(此 TID 來自上一步驟,該步驟在 $t<NUM>後面列出號碼)以獲取關於該線程的信息。
7. 從第 3 步驟的輸出中,在“general”下查找“pthread_t”,並記錄該十六進制號碼。
8. 非常重要說明:在 dbx 提示符下,您需要在完成操作時在 dbx 命令行鍵入“detach”,否則,如果您在連接到進程時只要一退出,dbx 將終止該進程!
9. 記下“p_thread_t”輸出中的十六進制值,並在 Thread Dump 中搜索其中哪個線程的“native ID”等於該值。
這將爲您揭示造成高 CPU 佔用率問題的線程。