您好,登錄后才能下訂單哦!
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 應用程序內存泄漏問題排查? ? ? ??
1.文章的由來;
?在日常運維過程中,會遇到服務器資源居高不下,或者CPU內存暴漲問題而引發的oom導致服務不可用 (大多數程序都是java應用),由此編寫了該文章,為工作排查問題參考依據和快速定位問題方法;
2.基礎知識儲備;
? ?(1).jvm 配置常見參數:
?堆參數參數
參數 | ?描述 |
-Xms? | ?設置?JVM啟動時堆內存的初始化大小 |
-Xmx | ?設置堆內存最大值 |
-Xmn | ?設置年輕代的空間大小,剩下的為年老代的空間大小 |
-XX:PermGen | ?設置永久代的內存初始化大小(JDK1.8 開始廢棄永久代) |
-XX:MaxPermGen | ?設置永久代的最大值 |
-XX:SurvivorRatio?? | ?設置Eden區和Survivor區的空間比例:Eden/S0 =Eden/S1 默認8 |
-XX:NewRatio | ?設置年老代和年輕代的比例大小,默認值是2 |
-XX:+UseSerialGC ? ? | 串行,young (年輕區)和Old(老年區)都使用串行,使用復制算法回收,邏輯簡單高效,無現場切換開銷 |
-XX:+UseParallelGC | 并行, young (年輕區)使用?Parallel scavenge 回收算法,會產生多個線程并行回收。通過-XX:ParallelGCThreads=n 參數指定有線程數,默認為cpu核數,Old(老年區):單線程 |
-XX:+UseParallelOldGC | 并行 和UseParallelGC 一樣,young (年輕區) 和Old(老年區) 垃圾回收都使用多線程收集 |
-XX:+UseConMarkSweepGC | 并發,短暫停頓的并發收集,young區可以使用普通的或者parallel垃圾回收算法,由參數 --XX:+UseParNewGC 來控制; Old區 只使用Concurrent Mark Sweep |
-XX:+UseG1GC | 并行的,并發的和增量式短暫停頓的垃圾收集器。不區分young (年輕區) 和Old(老年區)空間。它把堆空間分為多個大小相等的區域。當進行垃圾收集時,他會優先收集存活對象較少的區域。因此叫"Garbage First” |
如上表所示,目前主要有串行、并行和并發三種,對于大內存的應用而言,串行的性能太低,因此使用到的主要是并行和并發兩種。并行和并發 GC 的策略通過 UseParallelGC 和 UseConcMarkSweepGC 來指定,還有一些細節的配置參數用來配置策略的執行方式。例如:XX:ParallelGCThreads, XX:CMSInitiatingOccupancyFraction 等。 通常:Young 區對象回收只可選擇并行(耗時間),Old 區選擇并發(耗 CPU)。
生產環境服務配置參數
?
? ? 系統版本 | 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 調優原則
在調優之前,我們需要記住下面的原則:
多數的 Java 應用不需要在服務器上進行 GC 優化;
多數導致 GC 問題的 Java 應用,都不是因為我們參數設置錯誤,而是代碼問題;
在應用上線之前,先考慮將機器的 JVM 參數設置到最優(最適合);
減少創建對象的數量;
減少使用全局變量和大對象;
GC 優化是到最后不得已才采用的手段;
在實際使用中,分析 GC 情況優化代碼比優化 GC 參數要多得多。
將轉移到老年代的對象數量降低到最小;
減少 GC 的執行時間。
策略 1:將新對象預留在新生代,由于 Full GC 的成本遠高于 Minor GC,因此盡可能將對象分配在新生代是明智的做法,實際項目中根據 GC 日志分析新生代空間大小分配是否合理,適當通過“-Xmn”命令調節新生代大小,最大限度降低新對象直接進入老年代的情況。
策略 2:大對象進入老年代,雖然大部分情況下,將對象分配在新生代是合理的。但是對于大對象這種做法卻值得商榷,大對象如果首次在新生代分配可能會出現空間不足導致很多年齡不夠的小對象被分配的老年代,破壞新生代的對象結構,可能會出現頻繁的 full gc。因此,對于大對象,可以設置直接進入老年代(當然短命的大對象對于垃圾回收老說簡直就是噩夢)。-XX:PretenureSizeThreshold 可以設置直接進入老年代的對象大小。
策略 3:合理設置進入老年代對象的年齡,-XX:MaxTenuringThreshold 設置對象進入老年代的年齡大小,減少老年代的內存占用,降低 full gc 發生的頻率。
策略 4:設置穩定的堆大小,堆大小設置有兩個參數:-Xms 初始化堆大小,-Xmx 最大堆大小。
策略5:注意: 如果滿足下面的指標,則一般不需要進行 GC 優化:
MinorGC 執行時間不到50ms;
Minor GC 執行不頻繁,約10秒一次;
Full GC 執行時間不到1s;
Full GC 執行頻率不算頻繁,不低于10分鐘1次。
3.排查技巧戰
1.內存泄漏排查技巧
系統命令
1.登陸探測服務器,首先是 top free df 三連;
jstat 是一個非常強大的 JVM 監控工具,一般用法是:jstat [-options] pid interval
它支持的查看項有:
-class 查看類加載信息
-compile 編譯統計信息
-gc 垃圾回收信息
-gcXXX 各區域 GC 的詳細信息 如 -gcold
3.?jstat -gc pid [interval] 查看??java 進程的 GC 狀態;
關注指標:?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)
線程狀態好像也無異常,接下來分析堆文件。
內存堆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 系統安裝包如下:
https://www.eclipse.org/downloads/download.php?file=/mat/1.9.1/rcp/MemoryAnalyzer-1.9.1.20190826-win32.win32.x86_64.zip
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。