JVM 調優概述

JVM 調優概述

性能定義

吞吐量 - 指不考慮 GC 引起的停頓時間或內存消耗,垃圾收集器能支撐應用達到的最高性能指標。延遲 - 其度量標準是縮短由於垃圾啊收集引起的停頓時間或者完全消除因垃圾收集所引起的停頓,避免應用運行時發生抖動。內存佔用 - 垃圾收集器流暢運行所需要的內存數量。

調優原則

GC 優化的兩個目標:

將進入老年代的對象數量降到最低減少 Full GC 的執行時間

GC 優化的基本原則是:將不同的 GC 參數應用到兩個及以上的服務器上然後比較它們的性能,然後將那些被證明可以提高性能或減少 GC 執行時間的參數應用於最終的工作服務器上。

將進入老年代的對象數量降到最低

除了可以在 JDK7 及更高版本中使用的 G1 收集器以外,其他分代 GC 都是由 Oracle JVM 提供的。關於分代 GC,就是對象在 Eden 區被創建,隨後被轉移到 Survivor 區,在此之後剩餘的對象會被轉入老年代。也有一些對象由於佔用內存過大,在 Eden 區被創建後會直接被傳入老年代。老年代 GC 相對來說會比新生代 GC 更耗時,因此,減少進入老年代的對象數量可以顯著降低 Full GC 的頻率。你可能會以爲減少進入老年代的對象數量意味着把它們留在新生代,事實正好相反,新生代內存的大小是可以調節的。

降低 Full GC 的時間

Full GC 的執行時間比 Minor GC 要長很多,因此,如果在 Full GC 上花費過多的時間(超過 1s),將可能出現超時錯誤。


如果通過減小老年代內存來減少 Full GC 時間,可能會引起 OutOfMemoryError 或者導致 Full GC 的頻率升高。另外,如果通過增加老年代內存來降低 Full GC 的頻率,Full GC 的時間可能因此增加。

因此,你需要把老年代的大小設置成一個“合適”的值。


GC 優化需要考慮的 JVM 參數


類型參數描述堆內存大小-Xms啓動 JVM 時堆內存的大小-Xmx堆內存最大限制新生代空間大小-XX:NewRatio新生代和老年代的內存比-XX:NewSize新生代內存大小-XX:SurvivorRatioEden 區和 Survivor 區的內存比


GC 優化時最常用的參數是-Xms,-Xmx和-XX:NewRatio。-Xms和-Xmx參數通常是必須的,所以NewRatio的值將對 GC 性能產生重要的影響。


有些人可能會問如何設置永久代內存大小,你可以用-XX:PermSize和-XX:MaxPermSize參數來進行設置,但是要記住,只有當出現OutOfMemoryError錯誤時你才需要去設置永久代內存。


GC 優化的過程


GC 優化的過程和大多數常見的提升性能的過程相似,下面是筆者使用的流程:


1.監控 GC 狀態


你需要監控 GC 從而檢查系統中運行的 GC 的各種狀態。


2.分析監控結果後決定是否需要優化 GC


在檢查 GC 狀態後,你需要分析監控結構並決定是否需要進行 GC 優化。如果分析結果顯示運行 GC 的時間只有 0.1-0.3 秒,那麼就不需要把時間浪費在 GC 優化上,但如果運行 GC 的時間達到 1-3 秒,甚至大於 10 秒,那麼 GC 優化將是很有必要的。


但是,如果你已經分配了大約 10GB 內存給 Java,並且這些內存無法省下,那麼就無法進行 GC 優化了。在進行 GC 優化之前,你需要考慮爲什麼你需要分配這麼大的內存空間,如果你分配了 1GB 或 2GB 大小的內存並且出現了OutOfMemoryError,那你就應該執行**堆快照(heap dump)**來消除導致異常的原因。


注意:

**堆快照(heap dump)**是一個用來檢查 Java 內存中的對象和數據的內存文件。該文件可以通過執行 JDK 中的jmap命令來創建。在創建文件的過程中,所有 Java 程序都將暫停,因此,不要在系統執行過程中創建該文件。

你可以在互聯網上搜索 heap dump 的詳細說明。

3.設置 GC 類型/內存大小


如果你決定要進行 GC 優化,那麼你需要選擇一個 GC 類型並且爲它設置內存大小。此時如果你有多個服務器,請如上文提到的那樣,在每臺機器上設置不同的 GC 參數並分析它們的區別。


4.分析結果


在設置完 GC 參數後就可以開始收集數據,請在收集至少 24 小時後再進行結果分析。如果你足夠幸運,你可能會找到系統的最佳 GC 參數。如若不然,你還需要分析輸出日誌並檢查分配的內存,然後需要通過不斷調整 GC 類型/內存大小來找到系統的最佳參數。


5.如果結果令人滿意,將參數應用到所有服務器上並結束 GC 優化


如果 GC 優化的結果令人滿意,就可以將參數應用到所有服務器上,並停止 GC 優化。


在下面的章節中,你將會看到上述每一步所做的具體工作。


命令


jmap


jmap 即 JVM Memory Map。


jmap 用於生成 heap dump 文件。


如果不使用這個命令,還可以使用 -XX:+HeapDumpOnOutOfMemoryError 參數來讓虛擬機出現 OOM 的時候,自動生成 dump 文件。


jmap 不僅能生成 dump 文件,還可以查詢 finalize 執行隊列、Java 堆和永久代的詳細信息,如當前使用率、當前使用的是哪種收集器等。


命令格式:


jmap [option] LVMID


option 參數:


dump - 生成堆轉儲快照finalizerinfo - 顯示在 F-Queue 隊列等待 Finalizer 線程執行 finalizer 方法的對象heap - 顯示 Java 堆詳細信息histo - 顯示堆中對象的統計信息permstat - to print permanent generation statisticsF - 當-dump 沒有響應時,強制生成 dump 快照

示例:jmap -dump PID 生成堆快照


dump 堆到文件,format 指定輸出格式,live 指明是活着的對象,file 指定文件名


$ jmap -dump:live,format=b,file=dump.hprof 28920


Dumping heap to /home/xxx/dump.hprof ...


Heap dump file created


dump.hprof 這個後綴是爲了後續可以直接用 MAT(Memory Anlysis Tool)打開。


示例:jmap -heap 查看指定進程的堆信息


注意:使用 CMS GC 情況下,jmap -heap 的執行有可能會導致 java 進程掛起。


jmap -heap PID


[root@chances bin]# ./jmap -heap 12379


Attaching to process ID 12379, please wait...


Debugger attached successfully.


Server compiler detected.


JVM version is 17.0-b16


using thread-local object allocation.


Parallel GC with 6 thread(s)


Heap Configuration:


MinHeapFreeRatio = 40


MaxHeapFreeRatio = 70


MaxHeapSize = 83886080 (80.0MB)


NewSize = 1310720 (1.25MB)


MaxNewSize = 17592186044415 MB


OldSize = 5439488 (5.1875MB)


NewRatio = 2


SurvivorRatio = 8


PermSize = 20971520 (20.0MB)


MaxPermSize = 88080384 (84.0MB)


Heap Usage:


PS Young Generation


Eden Space:


capacity = 9306112 (8.875MB)


used = 5375360 (5.1263427734375MB)


free = 3930752 (3.7486572265625MB)


57.761608714788736% used


From Space:


capacity = 9306112 (8.875MB)


used = 3425240 (3.2665634155273438MB)


free = 5880872 (5.608436584472656MB)


36.80634834397007% used


To Space:


capacity = 9306112 (8.875MB)


used = 0 (0.0MB)


free = 9306112 (8.875MB)


0.0% used


PS Old Generation


capacity = 55967744 (53.375MB)


used = 48354640 (46.11457824707031MB)


free = 7613104 (7.2604217529296875MB)


86.39733629427693% used


PS Perm Generation


capacity = 62062592 (59.1875MB)


used = 60243112 (57.452308654785156MB)


free = 1819480 (1.7351913452148438MB)


97.06831451706046% used


jstack


jstack 用於生成 java 虛擬機當前時刻的線程快照。


線程快照是當前 java 虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等。


線程出現停頓的時候通過 jstack 來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在後臺做什麼事情,或者等待什麼資源。 如果 java 程序崩潰生成 core 文件,jstack 工具可以用來獲得 core 文件的 java stack 和 native stack 的信息,從而可以輕鬆地知道 java 程序是如何崩潰和在程序何處發生問題。另外,jstack 工具還可以附屬到正在運行的 java 程序中,看到當時運行的 java 程序的 java stack 和 native stack 的信息, 如果現在運行的 java 程序呈現 hung 的狀態,jstack 是非常有用的。


命令格式:


jstack [option] LVMID


option 參數:


-F - 當正常輸出請求不被響應時,強制輸出線程堆棧-l - 除堆棧外,顯示關於鎖的附加信息-m - 如果調用到本地方法的話,可以顯示 C/C++的堆棧

jps


jps(JVM Process Status Tool),顯示指定系統內所有的 HotSpot 虛擬機進程。


命令格式:


jps [options] [hostid]


option 參數:


-l - 輸出主類全名或 jar 路徑-q - 只輸出 LVMID-m - 輸出 JVM 啓動時傳遞給 main()的參數-v - 輸出 JVM 啓動時顯示指定的 JVM 參數

其中[option]、[hostid]參數也可以不寫。


$ jps -l -m


28920 org.apache.catalina.startup.Bootstrap start


11589 org.apache.catalina.startup.Bootstrap start


25816 sun.tools.jps.Jps -l -m


jstat


jstat(JVM statistics Monitoring),是用於監視虛擬機運行時狀態信息的命令,它可以顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT 編譯等運行數據。


命令格式:


jstat [option] LVMID [interval] [count]


參數:


[option] - 操作參數LVMID - 本地虛擬機進程 ID[interval] - 連續輸出的時間間隔[count] - 連續輸出的次數

jhat


jhat(JVM Heap Analysis Tool),是與 jmap 搭配使用,用來分析 jmap 生成的 dump,jhat 內置了一個微型的 HTTP/HTML 服務器,生成 dump 的分析結果後,可以在瀏覽器中查看。


注意:一般不會直接在服務器上進行分析,因爲 jhat 是一個耗時並且耗費硬件資源的過程,一般把服務器生成的 dump 文件複製到本地或其他機器上進行分析。


命令格式:


jhat [dumpfile]


jinfo


jinfo(JVM Configuration info),用於實時查看和調整虛擬機運行參數。


之前的 jps -v 口令只能查看到顯示指定的參數,如果想要查看未被顯示指定的參數的值就要使用 jinfo 口令


命令格式:


jinfo [option] [args] LVMID


option 參數:


-flag : 輸出指定 args 參數的值-flags : 不需要 args 參數,輸出所有 JVM 參數的值-sysprops : 輸出系統屬性,等同於 System.getProperties()

HotSpot VM 參數


詳細參數說明請參考官方文檔:Java HotSpot VM Options,這裏僅列舉常用參數。

JVM 內存配置


配置描述-Xms堆空間初始值。-Xmx堆空間最大值。-XX:NewSize新生代空間初始值。-XX:MaxNewSize新生代空間最大值。-Xmn新生代空間大小。-XX:PermSize永久代空間的初始值。-XX:MaxPermSize永久代空間的最大值。


GC 類型配置


配置描述-XX:+UseSerialGC串行垃圾回收器-XX:+UseParallelGC並行垃圾回收器-XX:+UseParNewGC使用 ParNew + Serial Old 垃圾回收器組合-XX:+UseConcMarkSweepGC併發標記掃描垃圾回收器-XX:ParallelCMSThreads=併發標記掃描垃圾回收器 = 爲使用的線程數量-XX:+UseG1GCG1 垃圾回收器


輔助配置


配置描述-XX:+PrintGCDetails打印 GC 日誌-Xloggc:<filename>指定 GC 日誌文件名-XX:+HeapDumpOnOutOfMemoryError內存溢出時輸出堆快照文件


典型配置


堆大小設置


年輕代的設置很關鍵。


JVM 中最大堆大小有三方面限制:


相關操作系統的數據模型(32-bt 還是 64-bit)限制;系統的可用虛擬內存限制;系統的可用物理內存限制。

整個堆大小 = 年輕代大小 + 年老代大小 + 持久代大小


持久代一般固定大小爲 64m。使用 -XX:PermSize 設置。官方推薦年輕代佔整個堆的 3/8。使用 -Xmn 設置。

回收器選擇


JVM 給了三種選擇:串行收集器、並行收集器、併發收集器。


JVM 實戰


分析 GC 日誌


獲取 GC 日誌


獲取 GC 日誌有兩種方式:


使用命令動態查看在容器中設置相關參數打印 GC 日誌

jstat -gc 統計垃圾回收堆的行爲:


jstat -gc 1262


S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT


26112.0 24064.0 6562.5 0.0 564224.0 76274.5 434176.0 388518.3 524288.0 42724.7 320 6.417 1 0.398 6.815


也可以設置間隔固定時間來打印:


$ jstat -gc 1262 2000 20


這個命令意思就是每隔 2000ms 輸出 1262 的 gc 情況,一共輸出 20 次


Tomcat 設置示例:


JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4


-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log


-Djava.awt.headless=true


-XX:+PrintGCTimeStamps -XX:+PrintGCDetails


-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000


-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"


-Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m Xms,即爲 jvm 啓動時得 JVM 初始堆大小,Xmx 爲 jvm 的最大堆大小,xmn 爲新生代的大小,permsize 爲永久代的初始大小,MaxPermSize 爲永久代的最大空間。-XX:SurvivorRatio=4 SurvivorRatio 爲新生代空間中的 Eden 區和救助空間 Survivor 區的大小比值,默認是 8,則兩個 Survivor 區與一個 Eden 區的比值爲 2:8,一個 Survivor 區佔整個年輕代的 1/10。調小這個參數將增大 survivor 區,讓對象儘量在 survitor 區呆長一點,減少進入年老代的對象。去掉救助空間的想法是讓大部分不能馬上回收的數據儘快進入年老代,加快年老代的回收頻率,減少年老代暴漲的可能性,這個是通過將-XX:SurvivorRatio 設置成比較大的值(比如 65536)來做到。-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log 將虛擬機每次垃圾回收的信息寫到日誌文件中,文件名由 file 指定,文件格式是平文件,內容和-verbose:gc 輸出內容相同。-Djava.awt.headless=true Headless 模式是系統的一種配置模式。在該模式下,系統缺少了顯示設備、鍵盤或鼠標。-XX:+PrintGCTimeStamps -XX:+PrintGCDetails 設置 gc 日誌的格式-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000 指定 rmi 調用時 gc 的時間間隔-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15 採用併發 gc 方式,經過 15 次 minor gc 後進入年老代

如何分析 GC 日誌


Young GC 回收日誌:


2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs]


Full GC 回收日誌:


2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]


通過上面日誌分析得出,PSYoungGen、ParOldGen、PSPermGen 屬於 Parallel 收集器。其中 PSYoungGen 表示 gc 回收前後年輕代的內存變化;ParOldGen 表示 gc 回收前後老年代的內存變化;PSPermGen 表示 gc 回收前後永久區的內存變化。young gc 主要是針對年輕代進行內存回收比較頻繁,耗時短;full gc 會對整個堆內存進行回城,耗時長,因此一般儘量減少 full gc 的次數


通過兩張圖非常明顯看出 gc 日誌構成:




OutOfMemory(OOM)分析


OutOfMemory ,即內存溢出,是一個常見的 JVM 問題。那麼分析 OOM 的思路是什麼呢?


首先,要知道有三種 OutOfMemoryError:


OutOfMemoryError:Java heap space - 堆空間溢出OutOfMemoryError:PermGen space - 方法區和運行時常量池溢出OutOfMemoryError:unable to create new native thread - 線程過多

OutOfMemoryError:PermGen space


OutOfMemoryError:PermGen space 表示方法區和運行時常量池溢出。


原因:


Perm 區主要用於存放 Class 和 Meta 信息的,Class 在被 Loader 時就會被放到 PermGen space,這個區域稱爲年老代。GC 在主程序運行期間不會對年老區進行清理,默認是 64M 大小。


當程序程序中使用了大量的 jar 或 class,使 java 虛擬機裝載類的空間不夠,超過 64M 就會報這部分內存溢出了,需要加大內存分配,一般 128m 足夠。


解決方案:


(1)擴大永久代空間


JDK7 以前使用 -XX:PermSize 和 -XX:MaxPermSize 來控制永久代大小。JDK8 以後把原本放在永久代的字符串常量池移出, 放在 Java 堆中(元空間 Metaspace)中,元數據並不在虛擬機中,使用的是本地的內存。使用 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 控制元空間大小。

注意:-XX:PermSize 一般設爲 64M

(2)清理應用程序中 WEB-INF/lib 下的 jar,用不上的 jar 刪除掉,多個應用公共的 jar 移動到 Tomcat 的 lib 目錄,減少重複加載。


OutOfMemoryError:Java heap space


OutOfMemoryError:Java heap space 表示堆空間溢出。


原因:JVM 分配給堆內存的空間已經用滿了。


問題定位


(1)使用 jmap 或 -XX:+HeapDumpOnOutOfMemoryError 獲取堆快照。 (2)使用內存分析工具(visualvm、mat、jProfile 等)對堆快照文件進行分析。 (3)根據分析圖,重點是確認內存中的對象是否是必要的,分清究竟是是內存泄漏(Memory Leak)還是內存溢出(Memory Overflow)。


內存泄露


內存泄漏是指由於疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。


內存泄漏並非指內存在物理上的消失,而是應用程序分配某段內存後,由於設計錯誤,失去了對該段內存的控制,因而造成了內存的浪費。


內存泄漏隨着被執行的次數越多-最終會導致內存溢出。


而因程序死循環導致的不斷創建對象-只要被執行到就會產生內存溢出。


內存泄漏常見幾個情況:


靜態集合類聲明爲靜態(static)的 HashMap、Vector 等集合通俗來講 A 中有 B,當前只把 B 設置爲空,A 沒有設置爲空,回收時 B 無法回收-因被 A 引用。

監聽器監聽器被註冊後釋放對象時沒有刪除監聽器

物理連接DataSource.getConnection()建立鏈接,必須通過 close()關閉鏈接

內部類和外部模塊等的引用發現它的方式同內存溢出,可再加個實時觀察jstat -gcutil 7362 2500 70

重點關注:


FGC — 從應用程序啓動到採樣時發生 Full GC 的次數。FGCT — 從應用程序啓動到採樣時 Full GC 所用的時間(單位秒)。FGC 次數越多,FGCT 所需時間越多-可非常有可能存在內存泄漏。

解決方案


(1)檢查程序,看是否有死循環或不必要地重複創建大量對象。有則改之。


下面是一個重複創建內存的示例:


public class OOM {


public static void main(String[] args) {


Integer sum1=300000;


Integer sum2=400000;


OOM oom = new OOM();


System.out.println("往ArrayList中加入30w內容");


oom.javaHeapSpace(sum1);


oom.memoryTotal();


System.out.println("往ArrayList中加入40w內容");


oom.javaHeapSpace(sum2);


oom.memoryTotal();


}


public void javaHeapSpace(Integer sum){


Random random = new Random();


ArrayList openList = new ArrayList();


for(int i=0;i<sum;i++){


String charOrNum = String.valueOf(random.nextInt(10));


openList.add(charOrNum);


}


}


public void memoryTotal(){


Runtime run = Runtime.getRuntime();


long max = run.maxMemory();


long total = run.totalMemory();


long free = run.freeMemory();


long usable = max - total + free;


System.out.println("最大內存 = " + max);


System.out.println("已分配內存 = " + total);


System.out.println("已分配內存中的剩餘空間 = " + free);


System.out.println("最大可用內存 = " + usable);


}


}


執行結果:


往ArrayList中加入30w內容


最大內存 = 20447232


已分配內存 = 20447232


已分配內存中的剩餘空間 = 4032576


最大可用內存 = 4032576


往ArrayList中加入40w內容


Exception in thread "main" java.lang.OutOfMemoryError: Java heap space


at java.util.Arrays.copyOf(Arrays.java:2245)


at java.util.Arrays.copyOf(Arrays.java:2219)


at java.util.ArrayList.grow(ArrayList.java:242)


at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)


at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)


at java.util.ArrayList.add(ArrayList.java:440)


at pers.qingqian.study.seven.OOM.javaHeapSpace(OOM.java:36)


at pers.qingqian.study.seven.OOM.main(OOM.java:26)


(2)擴大堆內存空間


使用 -Xms 和 -Xmx 來控制堆內存空間大小。


OutOfMemoryError: GC overhead limit exceeded


原因:JDK6 新增錯誤類型,當 GC 爲釋放很小空間佔用大量時間時拋出;一般是因爲堆太小,導致異常的原因,沒有足夠的內存。


解決方案:


查看系統是否有使用大內存的代碼或死循環; 通過添加 JVM 配置,來限制使用內存:


<jvm-arg>-XX:-UseGCOverheadLimit</jvm-arg>


OutOfMemoryError:unable to create new native thread


原因:線程過多


那麼能創建多少線程呢?這裏有一個公式:


(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads


MaxProcessMemory 指的是一個進程的最大內存


JVMMemory JVM內存


ReservedOsMemory 保留的操作系統內存


ThreadStackSize 線程棧的大小


當發起一個線程的創建時,虛擬機會在 JVM 內存創建一個 Thread 對象同時創建一個操作系統線程,而這個系統線程的內存用的不是 JVMMemory,而是系統中剩下的內存: (MaxProcessMemory - JVMMemory - ReservedOsMemory) 結論:你給 JVM 內存越多,那麼你能用來創建的系統線程的內存就會越少,越容易發生 java.lang.OutOfMemoryError: unable to create new native thread。


CPU 過高


定位步驟:


(1)執行 top -c 命令,找到 cpu 最高的進程的 id


(2)jstack PID 導出 Java 應用程序的線程堆棧信息。


示例:


jstack 6795


"Low Memory Detector" daemon prio=10 tid=0x081465f8 nid=0x7 runnable [0x00000000..0x00000000]


"CompilerThread0" daemon prio=10 tid=0x08143c58 nid=0x6 waiting on condition [0x00000000..0xfb5fd798]


"Signal Dispatcher" daemon prio=10 tid=0x08142f08 nid=0x5 waiting on condition [0x00000000..0x00000000]


"Finalizer" daemon prio=10 tid=0x08137ca0 nid=0x4 in Object.wait() [0xfbeed000..0xfbeeddb8]


at java.lang.Object.wait(Native Method)


- waiting on <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)


at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)


- locked <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)


at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)


at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)


"Reference Handler" daemon prio=10 tid=0x081370f0 nid=0x3 in Object.wait() [0xfbf4a000..0xfbf4aa38]


at java.lang.Object.wait(Native Method)


- waiting on <0xef600758> (a java.lang.ref.Reference$Lock)


at java.lang.Object.wait(Object.java:474)


at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)


- locked <0xef600758> (a java.lang.ref.Reference$Lock)


"VM Thread" prio=10 tid=0x08134878 nid=0x2 runnable


"VM Periodic Task Thread" prio=10 tid=0x08147768 nid=0x8 waiting on condition


在打印的堆棧日誌文件中,tid 和 nid 的含義:


nid : 對應的 Linux 操作系統下的 tid 線程號,也就是前面轉化的 16 進制數字


tid: 這個應該是 jvm 的 jmm 內存規範中的唯一地址定位


在 CPU 過高的情況下,查找響應的線程,一般定位都是用 nid 來定位的。而如果發生死鎖之類的問題,一般用 tid 來定位。


(3)定位 CPU 高的線程打印其 nid


查看線程下具體進程信息的命令如下:


top -H -p 6735


top - 14:20:09 up 611 days, 2:56, 1 user, load average: 13.19, 7.76, 7.82


Threads: 6991 total, 17 running, 6974 sleeping, 0 stopped, 0 zombie


%Cpu(s): 90.4 us, 2.1 sy, 0.0 ni, 7.0 id, 0.0 wa, 0.0 hi, 0.4 si, 0.0 st


KiB Mem: 32783044 total, 32505008 used, 278036 free, 120304 buffers


KiB Swap: 0 total, 0 used, 0 free. 4497428 cached Mem


PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND


6800 root 20 0 27.299g 0.021t 7172 S 54.7 70.1 187:55.61 java


6803 root 20 0 27.299g 0.021t 7172 S 54.4 70.1 187:52.59 java


6798 root 20 0 27.299g 0.021t 7172 S 53.7 70.1 187:55.08 java


6801 root 20 0 27.299g 0.021t 7172 S 53.7 70.1 187:55.25 java


6797 root 20 0 27.299g 0.021t 7172 S 53.1 70.1 187:52.78 java


6804 root 20 0 27.299g 0.021t 7172 S 53.1 70.1 187:55.76 java


6802 root 20 0 27.299g 0.021t 7172 S 52.1 70.1 187:54.79 java


6799 root 20 0 27.299g 0.021t 7172 S 51.8 70.1 187:53.36 java


6807 root 20 0 27.299g 0.021t 7172 S 13.6 70.1 48:58.60 java


11014 root 20 0 27.299g 0.021t 7172 R 8.4 70.1 8:00.32 java


10642 root 20 0 27.299g 0.021t 7172 R 6.5 70.1 6:32.06 java


6808 root 20 0 27.299g 0.021t 7172 S 6.1 70.1 159:08.40 java


11315 root 20 0 27.299g 0.021t 7172 S 3.9 70.1 5:54.10 java


12545 root 20 0 27.299g 0.021t 7172 S 3.9 70.1 6:55.48 java


23353 root 20 0 27.299g 0.021t 7172 S 3.9 70.1 2:20.55 java


24868 root 20 0 27.299g 0.021t 7172 S 3.9 70.1 2:12.46 java


9146 root 20 0 27.299g 0.021t 7172 S 3.6 70.1 7:42.72 java


由此可以看出佔用 CPU 較高的線程,但是這些還不高,無法直接定位到具體的類。nid 是 16 進制的,所以我們要獲取線程的 16 進制 ID:


printf "%x\n" 6800


輸出結果:45cd


然後根據輸出結果到 jstack 打印的堆棧日誌中查定位:


"catalina-exec-5692" daemon prio=10 tid=0x00007f3b05013800 nid=0x45cd waiting on condition [0x00007f3ae08e3000]


java.lang.Thread.State: TIMED_WAITING (parking)


at sun.misc.Unsafe.park(Native Method)


- parking to wait for <0x00000006a7800598> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)


at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)


at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2082)


at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)


at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:86)


at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:32)


at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)


at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)


at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)


at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)


at java.lang.Thread.run(Thread.java:745)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章