首先咱們經過top 命令進行分析,找出消耗最多cpu的java 進程id 。java
找出對應的進程id 後,咱們能夠經過 top -Hp 進程id 命令來找出該進程中佔用cpu最多的前幾個線程id。算法
咱們使用 jstack -l 進程pid > /tmp/java_pid.log 輸出java的堆棧日誌到文件 /tmp/java_pid.log。apache
咱們將剛剛查詢到的java進程中佔用cpu最多的前幾個線程id。進行轉化爲16進制。tomcat
printf "%X" 線程id
咱們在java堆棧日誌文件中找到上面轉化爲16進制的線程的pid對應的 日誌。bash
實際操做步驟流程圖: eclipse
補充:有時多是咱們代碼建立線程過多致使的問題:jvm
# 查看該進程有多少線程 ps p 9534 -L -o pcpu,pmem,pid,tid,time,tname,cmd|wc -l
咱們把對應的線程id的日誌拿給咱們的開發,進行定位錯誤,這裏容易定位出的錯誤是:
我能夠把定位到代碼位置,告訴開發,讓開發查看對應的代碼是否有問題。工具
Java 虛擬機在執行 Java 程序的過程當中會把它管理的內存劃分紅若干個不一樣的數據區域。
源碼分析
這些組成部分一些是線程私有的,其餘的則是線程共享的。
線程私有的:
線程共享的:
Java 堆是垃圾收集器管理的主要區域,所以也被稱做GC堆(Garbage Collected Heap).從垃圾回收的角度,因爲如今收集器基本都採用分代垃圾收集算法,因此Java堆還能夠細分爲:新生代和老年代:再細緻一點有:Eden空間、From Survivor、To Survivor空間等。進一步劃分的目的是更好地回收內存,或者更快地分配內存。
在 JDK 1.8中移除整個永久代,取而代之的是一個叫元空間(Metaspace)的區域(永久代使用的是JVM的堆內存空間,而元空間使用的是物理內存,直接受到本機的物理內存限制)。關於metaspace的詳細講解看:JVM源碼分析之Metaspace解密
java 實際的內存使用是這樣的,大多數狀況下,對象在新生代中 eden 區分配。當 eden 區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC(新生代GC).將eden 區的一些存活對象移動到Survivor 區,當Survivor區的大小,不夠儲存eden 區的存活對象時,那麼就會將它移動到老年區(Old Generation ),當老年區滿了時候將觸發一次 Full GC .
在實際工做中,咱們可使用 jmap -heap pid 來查看當前的進程的 java 堆的分佈狀況。
[root@iz23nb5ujp69 ~]# jmap -heap 11764 Attaching to process ID 11764, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.73-b02 using thread-local object allocation. Parallel GC with 2 thread(s) Heap Configuration: MinHeapFreeRatio = 0 #GC後,若是發現空閒堆內存佔到整個預估堆內存的40%,則放大堆內存的預估最大值,但不超過固定最大值。默認該值是40 MaxHeapFreeRatio = 100 #GC後,若是發現空閒堆內存佔到整個預估堆內存的100%,則收縮堆內存預估最大值。默認的是70 MaxHeapSize = 2147483648 (2048.0MB) # 最大的堆內存 NewSize = 715653120 (682.5MB) # 新生代初始大小 MaxNewSize = 715653120 (682.5MB) # 新生代最大大小 OldSize = 1431830528 (1365.5MB) #老年代 NewRatio = 2 # 新生代和老年代的 內存比例: 1:2 默認值 SurvivorRatio = 8 # Eden區與Survivor區的大小比值,設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10,Eden區和Survivor區 的實際比例值是會變更的 MetaspaceSize = 21807104 (20.796875MB) # 元空間大小 CompressedClassSpaceSize = 1073741824 (1024.0MB) # 壓縮時可用的最大的內存 MaxMetaspaceSize = 17592186044415 MB #元空間可用最大大小 G1HeapRegionSize = 0 (0.0MB) #G1 收集器的內存大小 Heap Usage: PS Young Generation # 新生代 Eden Space: # Eden capacity = 372768768 (355.5MB) used = 185979712 (177.36407470703125MB) free = 186789056 (178.13592529296875MB) 49.89144154909459% used # eden 可用區使用率,該值滿了將觸發 young gc From Space: # Survivor1 capacity = 175112192 (167.0MB) used = 47983120 (45.76026916503906MB) free = 127129072 (121.23973083496094MB) 27.401358781460516% used To Space: # Survivor2 capacity = 167772160 (160.0MB) used = 0 (0.0MB) free = 167772160 (160.0MB) 0.0% used PS Old Generation # 老年代 capacity = 1431830528 (1365.5MB) used = 257274632 (245.35620880126953MB) free = 1174555896 (1120.1437911987305MB) 17.9682320616089% used # 老年代 可用區使用率,該值滿了將觸發 full gc
適當的young gc 可讓清理一些不存活的對象,可是短期大量的 young GC 是會致使 Full GC 的,那麼Full gc 是儘可能不要產生的,當一個應用,產生大量的full GC是不正常的, 過多的GC和Full GC是會佔用不少的系統資源(主要是CPU),影響系統的吞吐量。
young gc:
Metadata GC
full gc :
GC 日誌解析
對了,如何在日誌中打印GC日誌,咱們在後面的配置中會講到。
[GC (Allocation Failure) [DefNew: 279616K->19156K(314560K), 0.0595827 secs] 279616K->19256K(1013632K), 0.0601044 secs] [Times: user=0.03 sys=0.02, real=0.06 secs] GC: 代表進行了一次垃圾回收,前面沒有Full修飾,代表這是一次Minor GC。 Allocation Failure: 代表本次引發GC的緣由是由於在年輕代中沒有足夠的空間可以存儲新的數據了。 279616K->19156K(314560K) 260460 三個參數分別爲:GC前該內存區域(這裏是年輕代)使用容量,GC後該內存區域使用容量,該內存區域總容量。 0.0595827 secs 表示GC耗時 279616K->19256K(1013632K) 堆區垃圾回收前的大小,堆區垃圾回收後的大小,堆區總大小。 0.0071945 secs Times: user=0.01 sys=0.00, real=0.01 secs 分別表示用戶態耗時,內核態耗時和總耗時 新生代清理的內存:279616 - 19156 = 260460k 堆區減小的內存:279616 - 19256 = 260360k 新生代存到老年代的 數據爲 260460k - 260360k
那麼如何查詢一個應用發生young Gc 和Full GC 的次數和耗時時間。咱們可使用jstat 。
jstat 查詢GC 次數和Full Gc 次數
jstat -gcutil pid 2000 10 (每隔2秒輸出一次結果,輸出10次) [root@iz23nb5ujp69 ~]# jstat -gcutil 3626 2000 10 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 56.01 0.00 8.21 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 8.30 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 8.41 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 8.56 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 9.00 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 9.27 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 9.34 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 9.46 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 9.57 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 56.01 0.00 9.70 88.19 98.44 96.92 27350 353.229 41 32.416 385.645 S0 — Heap上的 Survivor space 0 區已使用空間的百分比 S1 — Heap上的 Survivor space 1 區已使用空間的百分比 E — Heap上的 Eden space 區已使用空間的百分比 O — Heap上的 Old space 區已使用空間的百分比 M - 表示的是Klass Metaspace以及NoKlass Metaspace二者總共的使用率 CSS -表示的是NoKlass Metaspace的使用率 YGC — 從應用程序啓動到採樣時發生 Young GC 的次數 YGCT– 從應用程序啓動到採樣時 Young GC 所用的時間(單位秒) FGC — 從應用程序啓動到採樣時發生 Full GC 的次數 FGCT– 從應用程序啓動到採樣時 Full GC 所用的時間(單位秒) GCT — 從應用程序啓動到採樣時用於垃圾回收的總時間(單位秒) FGC Scavenge GC要慢,所以應該儘量減小Full GC。
致使young Gc 和 full GC 的緣由有哪些:
jvm 默認使用的配置
咱們拿咱們的tomcat 應用來講,咱們若是使用默認的配置,咱們使用jmap -heap 線程 查看
[root@www apache-tomcat-8.5.38]# jmap -heap 7568 Attaching to process ID 7568, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.131-b11 using thread-local object allocation. Mark Sweep Compact GC Heap Configuration: MinHeapFreeRatio = 40 # 這個默認值在上面已經說到了,對於heap,咱們儘可能不要讓它自動調整 MaxHeapFreeRatio = 70 # 這個默認值在上面已經說到了,對於heap,咱們儘可能不要讓它自動調整 MaxHeapSize = 480247808 (458.0MB) NewSize = 10485760 (10.0MB) MaxNewSize = 160038912 (152.625MB) OldSize = 20971520 (20.0MB) # 默認的值分配不合理 NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB)
上面的配置很大一部分是不合理的,對於線上應用來講。
JVM配置參數(根據本身的項目狀況調整)
-Xms2048m # 堆的最小內存,建議和最大內存設置的一致,以免每次垃圾回收完成後JVM從新分配內存,提升GC運行的效率。 -Xmx2048m # 堆的最大內存,建議和最小內存設置的一致,以免每次垃圾回收完成後JVM從新分配內存,提升GC運行的效率。 -XX:MaxHeapFreeRatio=100 #GC後,若是發現空閒堆內存佔到整個預估堆內存的100%,則收縮堆內存預估最大值。默認的是70 -XX:MinHeapFreeRatio=0 #GC後,若是發現空閒堆內存佔到整個預估堆內存的0%,則放大堆內存的預估最大值,但不超過固定最大值。默認該值是40 -Xmn900m #設置新生代的內存,若是咱們設置了Xmx 和Xms爲一致的話,那麼該值的默認值爲Xmx值的1/3。 -XX:MetaspaceSize=64M # 初始化的Metaspace大小,也是最小大小 java 8 後,用Meta代替了永久代,默認該值爲20M(因系統而異),若是日誌中出現了Meta GC,那麼能夠提升該值。 -XX:MaxMetaspaceSize= # 這個參數用於限制Metaspace增加的上限,防止由於某些狀況致使Metaspace無限的使用本地內存,影響到其餘程序。 -XX:MinMetaspaceFreeRatio=40 #GC後,若是發現空閒Meta內存佔到整個預估Meta內存的40%,則放大Meta內存的預估最大值,但不超過固定最大值。默認該值是40 -XX:MaxMetaspaceFreeRatio=70 #GC後,若是發現空閒Meta內存佔到整個預估Meta內存的70%,則收縮Meta內存預估最大值。默認的是70 -XX:MaxNewSize= #設置新生代的最大值,通常默認爲整個堆的1/3 -XX:NewRatio=N #設置新生代和老年代的比值,默認爲2 表示 新生代佔用1/3 -XX:SurvivorRatio=N #設置新生代中的 Eden 和兩個Survivor 的比值,默認爲8表示,eden佔用 8/10,時間中jvm會自動調整Eden 和Survivor 的值的。 -XX:MaxTenuringThreshold=N #新生代的對象的年齡(年齡計數器)達到N值後移動到老年代,默認值爲15 -XX:ParallelGCThreads=n #設置垃圾收集器在並行階段使用的線程數,建議設置爲與處理器數目相等 -XX:+DisableExplicitGC #關閉System.gc() 看你的程序是否須要System.gc(),再來決定 -XX:MaxDirectMemorySize # 來指定最大的堆外內存 選用GC 回收器,不一樣的回收器,對應的延遲和內存不一致 ## 針對Meta 細化設置 -XX:CompressedClassSpaceSize #這個參數主要是設置Klass Metaspace的大小,不過這個參數設置了也不必定起做用,前提是能開啓壓縮指針,假如-Xmx超過了32G,壓縮指針是開啓不來的。若是有Klass Metaspace,那這塊內存是和Heap連着的。 ## 垃圾收集器的選擇 Parallel Scavenge收集器關注點是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的關注點更多的是用戶線程的停頓時間(提升用戶體驗,減小回收的停頓時間),CMS GC算法主要是針對老生代,持久代。所謂吞吐量就是CPU中用於運行用戶代碼的時間與CPU總消耗時間的比值。還有G1 收集器是(java1.9的默認收集器)。jdk1.8 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代) 若是要使用CMS收集器的話, -XX:+UseConcMarkSweepGC # 開啓CMS GC 垃圾收集器 -XX:+UseCMSInitiatingOccupancyOnly #只有開啓了這個參數,CMSInitiatingOccupancyFraction這個參數纔會生效 -XX:CMSInitiatingOccupancyFraction= #觸發cms gc的老生代使用率,當老生代使用達到該閾值以後,就將觸發GC,該參數必須配合UseCMSInitiatingOccupancyOnly使用纔有效 -XX:+CMSClassUnloadingEnabled/-XX:-CMSClassUnloadingEnabled # 在使用CMS時,是否開啓類卸載 若是開啓 在full gc是會順帶掃描metaSpace/PermGen -XX:+ParallelRefProcEnabled # 儘可能開啓並行處理在任何地方 -XX:+CMSScavengeBeforeRemark #開啓在cms gc remark以前作一次ygc,減小gc roots掃描的對象數,從而提升remark的效率 -XX:+UseCMSCompactAtFullCollection # 默認開啓,在full gc時使用 Mark-Sweep-Compact 算法。
其它參數
-Xss256k # 每一個線程的堆棧大小,Xss越大,每一個線程的大小就越大,佔用的內存越多,能容納的線程就越少 #Xss越小,則遞歸的深度越小,容易出現棧溢出 java.lang.StackOverflowError,減小局部變量的聲明,能夠節省棧幀大小,增長調用深度 -XX:+PrintGCDetails # 日誌中輸入GC詳情日誌。 -XX:+PrintHeapAtGC # 打印GC先後的詳細堆棧信息 -XX:+PrintGCTimeStamps # 打印GC發生的時間戳 -XX:+PrintGCDateStamps # 輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) # 指定GC 日誌和錯誤日誌,OOM -XX:ErrorFile=/tmp/gc/hs_err_pid%p.log # 發生錯誤時錯誤日誌保存的地方 -Xloggc:/tmp/gc/gc.log # gc 日誌記錄的地方 -XX:+HeapDumpOnOutOfMemoryError #啓用當拋出OutOfMemoryError異常時,將堆轉儲到文件 -XX:HeapDumpPath=/tmp/gc #當啓用 HeapDumpOnOutOfMemoryError 時,儲存dump文件的路徑 -XX:+PrintGCApplicationStoppedTime # 啓用打印應用暫停的時間 -XX:+TraceClassLoading # 記錄你的類究竟是從哪一個文件加載進來的 還有一些參數見文章:https://blog.csdn.net/see__you__again/article/details/51998038
但願達成的: young gc 頻率適中,若是young gc 次數較少的話,一次young gc 的耗時就會比較長,那麼最求的平衡就是: young gc 頻率和 young gc 耗時 達到二者的平衡值。Full Gc 儘可能不要有。
參考配置
-Xms2048m -Xmx2048m -XX:MaxHeapFreeRatio=100 -XX:MinHeapFreeRatio=0 -Xmn900m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:MinMetaspaceFreeRatio=0 -XX:SurvivorRatio=7 -XX:MaxMetaspaceFreeRatio=100 -XX:MaxTenuringThreshold=14 -XX:ParallelGCThreads=2 -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintGCDetails
調優完以後的效果是 ,20-30分鐘發生一次Minor GC(young gc ) 每次的GC 耗時,無Full GC 和Meta 致使的GC。
案例分析:
GC 日誌
[GC (Metadata GC Threshold) [PSYoungGen: 282234K->101389K(523264K)] 589068K->410894K(1921536K), 0.1611903 secs] [Times: user=0.18 sys=0.00, real=0.16 secs] [Full GC (Metadata GC Threshold) [PSYoungGen: 101389K->0K(523264K)] [ParOldGen: 309505K->258194K(1398272K)] 410894K->258194K(1921536K), [Metaspace: 268611K->268101K(1294336K)], 1.8562117 secs] [Times: user=1.80 sys=0.08, real=1.86 secs]
咱們能夠在日誌中看到觸發了一次普通的GC 和一次 Full GC ,兩次GC的緣由都是Meta區GC致使的,咱們看Full Gc 的日誌, young 區的內存沒有使用完,old區的內存也沒有佔用滿,只有Meta 區的內存佔用滿了,那麼致使這個問題的就是Meta 區設置的過小。
擴展:
查看 java 的一些默認配置
java -XX:+PrintFlagsInitial 示例: 查看Meta(元空間的默認大小) java -XX:+PrintFlagsInitial |grep MetaspaceSize
從代碼層面的話,咱們就須要分析出 每一個程序的內存分佈狀況,每一個類的實例數,佔用內存最大的類,和他們活動的時長,是否有內存泄漏啊,內存泄漏的疑點。
內存泄漏:對象已經死了,沒法經過垃圾收集器進行自動回收,沒法釋放內存。
內存溢出:程序在申請內存時,沒有足夠的內存空間供其使用。
1 生成堆
分析的前提是咱們須要拿到分析的數據:
jmap -dump:live,format=b,file=/tmp/dump1628.dat pid
2 下載並安裝MAT
而後咱們可使用MAT分析工具進行分析堆文件。
MAT下載連接 :http://www.eclipse.org/mat/(須要java環境)
3 將堆信息導入到MAT 進行分析
內容借鑑於:https://www.cnblogs.com/AloneSword/p/3821569.html
Histogram: 列出內存中的對象,對象的個數以及大小。
Dominator Tree: 能夠列出那個線程,以及線程下面的那些對象佔用的空間.
Top Consumers: 以圖形的形式列出最大的object
Duplicate Classes: 列出一個類被多個類加載引用的類名
Leak Suspects: 包含疑似內存泄漏和系統概述的報告
Top Components:列出佔用超過總堆1%的組件
關於每個分類的詳情見博客:https://www.cnblogs.com/AloneSword/p/3821569.html
關於內存的調優,咱們還可使用工具 JConsole和Java VisualVM。 等等