VM 調優概述:java
性能定義:web
- 吞吐量 - 指不考慮 GC 引發的停頓時間或內存消耗,垃圾收集器能支撐應用達到的最高性能指標。
- 延遲 - 其度量標準是縮短因爲垃圾啊收集引發的停頓時間或者徹底消除因垃圾收集所引發的停頓,避免應用運行時發生抖動。
- 內存佔用 - 垃圾收集器流暢運行所須要的內存數量。
調優原則apache
GC 優化的兩個目標:瀏覽器
- 將進入老年代的對象數量降到最低
- 減小 Full GC 的執行時間
GC 優化的基本原則是:將不一樣的 GC 參數應用到兩個及以上的服務器上而後比較它們的性能,而後將那些被證實能夠提升性能或減小 GC 執行時間的參數應用於最終的工做服務器上。tomcat
將進入老年代的對象數量降到最低服務器
除了能夠在 JDK7 及更高版本中使用的 G1 收集器之外,其餘分代 GC 都是由 Oracle JVM 提供的。關於分代 GC,就是對象在 Eden 區被建立,隨後被轉移到 Survivor 區,在此以後剩餘的對象會被轉入老年代。也有一些對象因爲佔用內存過大,在 Eden 區被建立後會直接被傳入老年代。老年代 GC 相對來講會比新生代 GC 更耗時,所以,減小進入老年代的對象數量能夠顯著下降 Full GC 的頻率。你可能會覺得減小進入老年代的對象數量意味着把它們留在新生代,事實正好相反,新生代內存的大小是能夠調節的。併發
下降 Full GC 的時間app
Full GC 的執行時間比 Minor GC 要長不少,所以,若是在 Full GC 上花費過多的時間(超過 1s),將可能出現超時錯誤。less
- 若是經過減少老年代內存來減小 Full GC 時間,可能會引發 OutOfMemoryError 或者致使 Full GC 的頻率升高。
- 另外,若是經過增長老年代內存來下降 Full GC 的頻率,Full GC 的時間可能所以增長。
所以,你須要把老年代的大小設置成一個「合適」的值。dom
GC 優化須要考慮的 JVM 參數
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 堆和永久代的詳細信息,如當前使用率、當前使用的是哪一種收集器等。
命令格式:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`jmap [option] LVMID`
</pre>
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 指定文件名
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`$ jmap -dump:live,format=b,file=dump.hprof 28920
Dumping heap to /home/xxx/dump.hprof ...
Heap dump file created`
</pre>
dump.hprof 這個後綴是爲了後續能夠直接用 MAT(Memory Anlysis Tool)打開。
示例:jmap -heap 查看指定進程的堆信息
注意:使用 CMS GC 狀況下,jmap -heap 的執行有可能會致使 java 進程掛起。
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">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
</pre>
jstack
jstack 用於生成 java 虛擬機當前時刻的線程快照。
線程快照是當前 java 虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的緣由,如線程間死鎖、死循環、請求外部資源致使的長時間等待等。
線程出現停頓的時候經過 jstack 來查看各個線程的調用堆棧,就能夠知道沒有響應的線程到底在後臺作什麼事情,或者等待什麼資源。 若是 java 程序崩潰生成 core 文件,jstack 工具能夠用來得到 core 文件的 java stack 和 native stack 的信息,從而能夠輕鬆地知道 java 程序是如何崩潰和在程序何處發生問題。另外,jstack 工具還能夠附屬到正在運行的 java 程序中,看到當時運行的 java 程序的 java stack 和 native stack 的信息, 若是如今運行的 java 程序呈現 hung 的狀態,jstack 是很是有用的。
命令格式:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`jstack [option] LVMID`
</pre>
option 參數:
- -F - 當正常輸出請求不被響應時,強制輸出線程堆棧
- -l - 除堆棧外,顯示關於鎖的附加信息
- -m - 若是調用到本地方法的話,能夠顯示 C/C++的堆棧
jps
jps(JVM Process Status Tool),顯示指定系統內全部的 HotSpot 虛擬機進程。
命令格式:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`jps [options] [hostid]`
</pre>
option 參數:
- -l - 輸出主類全名或 jar 路徑
- -q - 只輸出 LVMID
- -m - 輸出 JVM 啓動時傳遞給 main()的參數
- -v - 輸出 JVM 啓動時顯示指定的 JVM 參數
其中[option]、[hostid]參數也能夠不寫。
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`$ jps -l -m
28920 org.apache.catalina.startup.Bootstrap start
11589 org.apache.catalina.startup.Bootstrap start
25816 sun.tools.jps.Jps -l -m`
</pre>
jstat
jstat(JVM statistics Monitoring),是用於監視虛擬機運行時狀態信息的命令,它能夠顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT 編譯等運行數據。
命令格式:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`jstat [option] LVMID [interval] [count]`
</pre>
參數:
- [option] - 操做參數
- LVMID - 本地虛擬機進程 ID
- [interval] - 連續輸出的時間間隔
- [count] - 連續輸出的次數
jhat
jhat(JVM Heap Analysis Tool),是與 jmap 搭配使用,用來分析 jmap 生成的 dump,jhat 內置了一個微型的 HTTP/HTML 服務器,生成 dump 的分析結果後,能夠在瀏覽器中查看。
注意:通常不會直接在服務器上進行分析,由於 jhat 是一個耗時而且耗費硬件資源的過程,通常把服務器生成的 dump 文件複製到本地或其餘機器上進行分析。
命令格式:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`jhat [dumpfile]`
</pre>
jinfo
jinfo(JVM Configuration info),用於實時查看和調整虛擬機運行參數。
以前的 jps -v 口令只能查看到顯示指定的參數,若是想要查看未被顯示指定的參數的值就要使用 jinfo 口令
命令格式:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`jinfo [option] [args] LVMID`
</pre>
option 參數:
- -flag : 輸出指定 args 參數的值
- -flags : 不須要 args 參數,輸出全部 JVM 參數的值
- -sysprops : 輸出系統屬性,等同於 System.getProperties()
HotSpot VM 參數
詳細參數說明請參考官方文檔:Java HotSpot VM Options,這裏僅列舉經常使用參數。
JVM 內存配置
GC 類型配置
輔助配置
典型配置
堆大小設置
年輕代的設置很關鍵。
JVM 中最大堆大小有三方面限制:
- 相關操做系統的數據模型(32-bt 仍是 64-bit)限制;
- 系統的可用虛擬內存限制;
- 系統的可用物理內存限制。
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; color: rgb(0, 0, 0); font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">整個堆大小 = 年輕代大小 + 年老代大小 + 持久代大小 </pre>
- 持久代通常固定大小爲 64m。使用 -XX:PermSize 設置。
- 官方推薦年輕代佔整個堆的 3/8。使用 -Xmn 設置。
回收器選擇
JVM 給了三種選擇:串行收集器、並行收集器、併發收集器。
JVM 實戰
分析 GC 日誌
獲取 GC 日誌
獲取 GC 日誌有兩種方式:
- 使用命令動態查看
- 在容器中設置相關參數打印 GC 日誌
jstat -gc 統計垃圾回收堆的行爲:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`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`
</pre>
也能夠設置間隔固定時間來打印:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`$ jstat -gc 1262 2000 20`
</pre>
這個命令意思就是每隔 2000ms 輸出 1262 的 gc 狀況,一共輸出 20 次
Tomcat 設置示例:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`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"`
</pre>
- -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 回收日誌:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`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]`
</pre>
Full GC 回收日誌:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`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]`
</pre>
經過上面日誌分析得出,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)檢查程序,看是否有死循環或沒必要要地重複建立大量對象。有則改之。
下面是一個重複建立內存的示例:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">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);
}
}
</pre>
執行結果:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`往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)`
</pre>
(2)擴大堆內存空間
使用 -Xms 和 -Xmx 來控制堆內存空間大小。
OutOfMemoryError: GC overhead limit exceeded
緣由:JDK6 新增錯誤類型,當 GC 爲釋放很小空間佔用大量時間時拋出;通常是由於堆過小,致使異常的緣由,沒有足夠的內存。
解決方案:
查看系統是否有使用大內存的代碼或死循環; 經過添加 JVM 配置,來限制使用內存:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
"><jvm-arg>-XX:-UseGCOverheadLimit</jvm-arg>
</pre>
OutOfMemoryError:unable to create new native thread
緣由:線程過多
那麼能建立多少線程呢?這裏有一個公式:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
MaxProcessMemory 指的是一個進程的最大內存
JVMMemory JVM內存
ReservedOsMemory 保留的操做系統內存
ThreadStackSize 線程棧的大小`
</pre>
當發起一個線程的建立時,虛擬機會在 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 應用程序的線程堆棧信息。
示例:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`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`
</pre>
在打印的堆棧日誌文件中,tid 和 nid 的含義:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`nid:對應的 Linux 操做系統下的 tid 線程號,也就是前面轉化的 16 進制數字
tid:這個應該是 jvm 的 jmm 內存規範中的惟一地址定位`
</pre>
在 CPU 太高的狀況下,查找響應的線程,通常定位都是用 nid 來定位的。而若是發生死鎖之類的問題,通常用 tid 來定位。
(3)定位 CPU 高的線程打印其 nid
查看線程下具體進程信息的命令以下:
top -H -p 6735
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`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`
</pre>
由此能夠看出佔用 CPU 較高的線程,可是這些還不高,沒法直接定位到具體的類。nid 是 16 進制的,因此咱們要獲取線程的 16 進制 ID:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`printf "%x " 6800`
</pre>
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`輸出結果:45cd`
</pre>
而後根據輸出結果到 jstack 打印的堆棧日誌中查定位:
<pre style="margin:0px;
padding:0px;
white-space:pre-wrap;
overflow-wrap:break-word;
color:rgb(0,0,0);
font-size:12px;
font-style:normal;
font-variant-ligatures:normal;
font-variant-caps:normal;
font-weight:400;
letter-spacing:normal;
orphans:2;
text-align:left;
text-indent:0px;
text-transform:none;
widows:2;
word-spacing:0px;
-webkit-text-stroke-width:0px;
background-color:rgb(255,255,255);
text-decoration-style:initial;
text-decoration-color:initial;
">`"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)`
</pre>