您好,登錄后才能下訂單哦!
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 應用程序內存泄漏問題排查? ? ? ??
1.文章的由來;
?在日常運維過程中,會遇到服務器資源居高不下,或者CPU內存暴漲問題而引發(fā)的oom導致服務不可用 (大多數程序都是java應用),由此編寫了該文章,為工作排查問題參考依據和快速定位問題方法;
2.基礎知識儲備;
? ?(1).jvm 配置常見參數:
?堆參數參數
參數 | ?描述 |
-Xms? | ?設置?JVM啟動時堆內存的初始化大小 |
-Xmx | ?設置堆內存最大值 |
-Xmn | ?設置年輕代的空間大小,剩下的為年老代的空間大小 |
-XX:PermGen | ?設置永久代的內存初始化大小(JDK1.8 開始廢棄永久代) |
-XX:MaxPermGen | ?設置永久代的最大值 |
-XX:SurvivorRatio?? | ?設置Eden區(qū)和Survivor區(qū)的空間比例:Eden/S0 =Eden/S1 默認8 |
-XX:NewRatio | ?設置年老代和年輕代的比例大小,默認值是2 |
-XX:+UseSerialGC ? ? | 串行,young (年輕區(qū))和Old(老年區(qū))都使用串行,使用復制算法回收,邏輯簡單高效,無現場切換開銷 |
-XX:+UseParallelGC | 并行, young (年輕區(qū))使用?Parallel scavenge 回收算法,會產生多個線程并行回收。通過-XX:ParallelGCThreads=n 參數指定有線程數,默認為cpu核數,Old(老年區(qū)):單線程 |
-XX:+UseParallelOldGC | 并行 和UseParallelGC 一樣,young (年輕區(qū)) 和Old(老年區(qū)) 垃圾回收都使用多線程收集 |
-XX:+UseConMarkSweepGC | 并發(fā),短暫停頓的并發(fā)收集,young區(qū)可以使用普通的或者parallel垃圾回收算法,由參數 --XX:+UseParNewGC 來控制; Old區(qū) 只使用Concurrent Mark Sweep |
-XX:+UseG1GC | 并行的,并發(fā)的和增量式短暫停頓的垃圾收集器。不區(qū)分young (年輕區(qū)) 和Old(老年區(qū))空間。它把堆空間分為多個大小相等的區(qū)域。當進行垃圾收集時,他會優(yōu)先收集存活對象較少的區(qū)域。因此叫"Garbage First” |
如上表所示,目前主要有串行、并行和并發(fā)三種,對于大內存的應用而言,串行的性能太低,因此使用到的主要是并行和并發(fā)兩種。并行和并發(fā) GC 的策略通過 UseParallelGC 和 UseConcMarkSweepGC 來指定,還有一些細節(jié)的配置參數用來配置策略的執(zhí)行方式。例如:XX:ParallelGCThreads, XX:CMSInitiatingOccupancyFraction 等。 通常:Young 區(qū)對象回收只可選擇并行(耗時間),Old 區(qū)選擇并發(fā)(耗 CPU)。
生產環(huán)境服務配置參數
?
? ? 系統(tǒng)版本 | cpu | mem | jdk 版本 | jvm配置 | 備注 |
CentOS 7.5.1804 (Core)? | 8C | 8G | java-1.8.0_121 | java? -Xmx5g -Xms5g -Xmn3g -XX:MetaspaceSize=32m -XX:MaxMetaspaceSize=128m -XX:MaxDirectMemorySize=512m -XX:+UseCompressedClassPointers -XX:CompressedClassSpaceSize=64m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 -XX:+CMSClassUnloadingEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=80 -verbose:gc -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+PrintCommandLineFlags -XX:+ExplicitGCInvokesConcurrent -Xloggc:/data/log/app-web/gc.log -Dapp.id=app-web -Dapollo.bootstrap.enabled=true -Duser.timezone=GMT+8 -Dapollo.bootstrap.namespaces=application -Deureka.instance.metadata-map.zone=zone-1 -Dservice.name=app-web -Denv=prod -Dprod_meta=http://configserver-prod.com -Druntime.env=prod -jar ./lib/app-web-1.8.10.0.jar | ? 微服務 springcloud 框架 |
常見組合
GC 調優(yōu)原則
在調優(yōu)之前,我們需要記住下面的原則:
多數的 Java 應用不需要在服務器上進行 GC 優(yōu)化;
多數導致 GC 問題的 Java 應用,都不是因為我們參數設置錯誤,而是代碼問題;
在應用上線之前,先考慮將機器的 JVM 參數設置到最優(yōu)(最適合);
減少創(chuàng)建對象的數量;
減少使用全局變量和大對象;
GC 優(yōu)化是到最后不得已才采用的手段;
在實際使用中,分析 GC 情況優(yōu)化代碼比優(yōu)化 GC 參數要多得多。
將轉移到老年代的對象數量降低到最??;
減少 GC 的執(zhí)行時間。
策略 1:將新對象預留在新生代,由于 Full GC 的成本遠高于 Minor GC,因此盡可能將對象分配在新生代是明智的做法,實際項目中根據 GC 日志分析新生代空間大小分配是否合理,適當通過“-Xmn”命令調節(jié)新生代大小,最大限度降低新對象直接進入老年代的情況。
策略 2:大對象進入老年代,雖然大部分情況下,將對象分配在新生代是合理的。但是對于大對象這種做法卻值得商榷,大對象如果首次在新生代分配可能會出現空間不足導致很多年齡不夠的小對象被分配的老年代,破壞新生代的對象結構,可能會出現頻繁的 full gc。因此,對于大對象,可以設置直接進入老年代(當然短命的大對象對于垃圾回收老說簡直就是噩夢)。-XX:PretenureSizeThreshold 可以設置直接進入老年代的對象大小。
策略 3:合理設置進入老年代對象的年齡,-XX:MaxTenuringThreshold 設置對象進入老年代的年齡大小,減少老年代的內存占用,降低 full gc 發(fā)生的頻率。
策略 4:設置穩(wěn)定的堆大小,堆大小設置有兩個參數:-Xms 初始化堆大小,-Xmx 最大堆大小。
策略5:注意: 如果滿足下面的指標,則一般不需要進行 GC 優(yōu)化:
MinorGC 執(zhí)行時間不到50ms;
Minor GC 執(zhí)行不頻繁,約10秒一次;
Full GC 執(zhí)行時間不到1s;
Full GC 執(zhí)行頻率不算頻繁,不低于10分鐘1次。
3.排查技巧戰(zhàn)
1.內存泄漏排查技巧
系統(tǒng)命令
1.登陸探測服務器,首先是 top free df 三連;
jstat 是一個非常強大的 JVM 監(jiān)控工具,一般用法是:jstat [-options] pid interval
它支持的查看項有:
-class 查看類加載信息
-compile 編譯統(tǒng)計信息
-gc 垃圾回收信息
-gcXXX 各區(qū)域 GC 的詳細信息 如 -gcold
3.?jstat -gc pid [interval] 查看??java 進程的 GC 狀態(tài);
關注指標:?FULL GC 。
4.?jstack pid > jstack.log 查詢線程棧 并保存現場;
棧的分析很簡單,看一下線程數是不是過多,多數棧都在干嘛。
> grep 'java.lang.Thread.State' jstack.log ?| wc -l
> 464
過濾進程
grep -A 1 'java.lang.Thread.State' jstack.log ?| grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n
? ? 10 ? ? at java.lang.Class.forName0(Native Method)
? ? 10 ? ? at java.lang.Object.wait(Native Method)
? ? 16 ? ? at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
? ? 44 ? ? at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
? ?344 ? ? at sun.misc.Unsafe.park(Native Method)
線程狀態(tài)好像也無異常,接下來分析堆文件。
內存堆dump??
5.使用 jmap -dump:format=b,file=heap.log pid 保存了堆現場,然后重啟了應用服務
堆文件都是一些二進制數據,在命令行查看非常麻煩,Java 為我們提供的工具都是可視化的,Linux 服務器上又沒法查看,那么首先要把文件下載到本地。
由于我們設置的堆內存為 4G,所以 dump 出來的堆文件也很大,下載它確實非常費事,不過我們可以先對它進行一次壓縮。=
gzip 是個功能很強大的壓縮命令,特別是我們可以設置 -1 ~ -9 來指定它的壓縮級別,數據越大壓縮比率越大,耗時也就越長,推薦使用 -6~7, -9 實在是太慢了,且收益不大,有這個壓縮的時間,多出來的文件也下載好了。
MAT 是分析 Java 堆內存的利器,使用它打開我們的堆文件(將文件后綴改為 .hprof), 它會提示我們要分析的種類,果斷選擇 memory leak suspect。
windows 系統(tǒng)安裝包如下:
https://www.eclipse.org/downloads/download.php?file=/mat/1.9.1/rcp/MemoryAnalyzer-1.9.1.20190826-win32.win32.x86_64.zip
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。