1. 程式人生 > >阿里面試100%問到,JVM效能調優篇

阿里面試100%問到,JVM效能調優篇

JVM 調優概述

效能定義

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

調優原則

GC 優化的兩個目標:

  1. 將進入老年代的物件數量降到最低
  2. 減少 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:SurvivorRatio Eden 區和 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 statistics
  • F - 當-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:+UseG1GC G1 垃圾回收器

輔助配置

配置 描述
-XX:+PrintGCDetails 列印 GC 日誌
-Xloggc:<filename> 指定 GC 日誌檔名
-XX:+HeapDumpOnOutOfMemoryError 記憶體溢位時輸出堆快照檔案

典型配置

堆大小設定

年輕代的設定很關鍵。

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

  1. 相關作業系統的資料模型(32-bt 還是 64-bit)限制;
  2. 系統的可用虛擬記憶體限制;
  3. 系統的可用實體記憶體限制。
整個堆大小 = 年輕代大小 + 年老代大小 + 持久代大小
  • 持久代一般固定大小為 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)

免費Java資料需要自己領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分散式等教程,一共30G。
傳送門:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

相關推薦

阿里面試100%JVM效能調

JVM 調優概述 效能定義 吞吐量 - 指不考慮 GC 引起的停頓時間或記憶體消耗,垃圾收集器能支撐應用達到的最高效

ifeve.com 南方《JVM 效能調實戰之:使用阿里開源工具 TProfiler 在海量業務程式碼中精確定位效能程式碼》

https://blog.csdn.net/defonds/article/details/52598018 多次拉取 JStack,發現很多執行緒處於這個狀態:    at jrockit/vm/Allocator.getNewTla(JJ)V(Native Method) 

JVM效能調監控工具jps、jstack、jmap、jhat、jstat、hprof使用詳解以及例子

現實企業級Java開發中,有時候我們會碰到下面這些問題: OutOfMemoryError,記憶體不足 記憶體洩露 執行緒死鎖 鎖爭用(Lock Contention) Java程序消耗CPU過高 ......     這

JVM 效能調實戰之 使用阿里開源工具 TProfiler 在海量業務程式碼中精確定位效能程式碼

                本文是《JVM 效能調優實戰之:一次系統性能瓶頸的尋找過程》 的後續篇,該篇介紹瞭如何使用 JDK 自身提供的工具進行 JVM 調優將 TPS 由 2.5 提升到 20 (提升了 7 倍),並準確定位系統瓶頸:我們應用裡靜態物件不是太多、有大量的業務執行緒在頻繁建立一些生命週期

JVM記憶體模型以及JVM效能調

轉載批註:最近因與別人討論問題時,問到JVM記憶體模型,但是苦於只知道JVM的大概內容,不知道詳細,也罷,近期會逐漸有充足的自己的時間,好好整理學習學習。以下內容為轉載別人的資料,個人認為寫的很好,就全文拷貝了。 ===================華麗的分割線==

JVM效能調的6大步驟及關鍵調引數詳解

JVM效能調優的6大步驟,及關鍵調優引數詳解 JVM效能調優方法和步驟 1.監控GC的狀態 2.生成堆的dump檔案 3.分析dump檔案 4.分析結果,判斷是否需要優化 5.調整GC型別和記憶體分配 6.不斷分析

JVM效能調監控工具jps、jstack、jstat、jmap、jinfo使用

 現實企業級Java開發中,有時候我們會碰到下面這些問題: OutOfMemoryError,記憶體不足 記憶體洩露 執行緒死鎖 鎖爭用(Lock Contention) Java程序消耗CPU過高 ...... &n

JVM效能調監控工具jps、jstack、jmap、jhat、jstat、hprof詳解

來源:https://my.oschina.net/feichexia/blog/196575 現實企業級Java開發中,有時候我們會碰到下面這些問題: OutOfMemoryError,記憶體不足 記憶體洩露 執行緒死鎖 鎖爭用(Lock Contention)

如何合理的規劃一次jvm效能調

原文中的評論很有參考價值,轉發只是為了以後方便檢視 這是jvm優化系列第三篇: JVM效能調優涉及到方方面面的取捨,往往是牽一髮而動全身,需要全盤考慮各方面的影響。但也有一些基礎的理論和原則,理解這些理論並遵循這些原則會讓你的效能調優任務將會更加輕鬆。為了更好的理解本

深入理解Java虛擬機器(四)——JVM效能調監控工具

Jinfo 檢視正在執行的Java應用程式的擴充套件引數 檢視jvm的引數 檢視java系統引數 Jstat jstat命令可以檢視堆記憶體各部分的使用量,以及載入類的數量。命

JVM效能調

 一、JVM記憶體模型及垃圾收集演算法  1.根據Java虛擬機器規範,JVM將記憶體劃分為: New(年輕代)Tenured(年老代)永久代(Perm)  其中New和Tenured屬於堆記憶體,堆記憶體會從JVM啟動引數(-Xmx:3G)指定的記憶體中分配,Perm

tomcat與JVM效能調

tomcat的效能調優是實際生產中很重要的一部分,雖然我們平時在除錯時只要能跑起來就行,但是實際部署之後,當訪問的使用者量一增加,就涉及到tomcat的最大併發量等問題。那麼如何設定tomcat以及JVM,使我們的web應用的併發量增加呢? 一、tomcat記憶體

1分鐘帶你入門JVM效能調

版本:JDK8 一、閱讀前熱身: 1、瞭解jvm啟動流程: 2、瞭解硬體、系統、程序三個層面的記憶體之間的概要記憶體分配,一張圖你就懂: 3、下面是需要背住的重點,敲黑板!!堆記憶體分配,想了解引數的可以到最下面看下備註和建議: 先來個日誌(看

深入理解JVM——JVM效能調實戰

如何在高效能伺服器上進行JVM調優? 為了充分利用高效能伺服器的硬體資源,有兩種JVM調優方案,它們都有各自的優缺點,需要根據具體的情況進行選擇。   採用64位作業系統,併為JVM分配大記憶體 我們知道,如果JVM中堆記憶體太小,那麼就會頻繁地發生垃圾回收

視訊:深入理解Java虛擬機器(jvm效能調+記憶體模型+虛擬機器原理)共110集

龍果學院深入理解Java虛擬機器(Jvm效能調優+記憶體模型+虛擬機器原視訊 Java虛擬機器視訊教程一套不錯的視訊,課程一共有110課,課程目錄較多隻展示部分出來,喜歡的朋友下載看下 課程目錄(課程較多,只展示部分目錄) 課程大綱 第1節說在前面的話 [免費觀看]

遊戲伺服器JVM效能調

最近開始優化頁遊服務端的效能,一些心得總結一下。現在的伺服器硬體越來越好,幾十G記憶體,十幾個CPU。當硬體不是瓶頸的時候,如果讓程式發揮最大效用就成了我們需要考慮的問題。就遊戲伺服器來說,得滿足幾

JVM 效能調實戰之:一次系統性能瓶頸的尋找過程

玩過效能優化的朋友都清楚,效能優化的關鍵並不在於怎麼進行優化,而在於怎麼找到當前系統的效能瓶頸。效能優化分為好幾個層次,比如系統層次、演算法層次、程式碼層次...JVM 的效能優化被認為是底層優化,門檻較高,精通這種技能的人比較少。筆者呆過幾家技術力量不算弱的公司,每個公司內

JVM效能調之生成堆的dump檔案

 最近因專案存在記憶體洩漏,故進行大規模的JVM效能調優 , 現把經驗做一記錄。 一、JVM記憶體模型及垃圾收集演算法  1.根據Java虛擬機器規範,JVM將記憶體劃分為: New(年輕代)Tenured(年老代)永久代(Perm)   其中New和Tenured屬

一次jVM效能調記錄

前言 填別人留下來的坑其實挺無奈的,會被搞的特別煩,特別是我這種要填三四個人留下來的坑的時候,滿滿的都是無奈。 幸好的是填坑也可以選擇一種更能提升自己的方式來填。 這次遇到的一個程式,是一個從kafka消費並且插入mysql的程式,該程式歷經三人之手,頻頻

jvm效能調都做了什麼

JVM效能調優有很多設定,這個參考JVM引數即可. 主要調優的目的: 控制GC的行為.GC是一個後臺處理,但是它也是會消耗系統性能的,因此經常會根據系統執行的程式的特性來更改GC行為 控制JVM堆疊大小.一般來說,JVM在記憶體分配上不需要你修改,(舉例)但是當