在遇到實際性能問題時,除了關注系統性能指標。還要結合應用程序的系統的日誌、堆棧信息、GClog、threaddump等數據進行問題分析和定位。關於性能指標分析能夠參考前一篇JVM性能調優實踐——性能指標分析。java
JVM的調優和故障處理可使用JDK的幾個經常使用命令工具。由於本文是基於Docker容器內部的Springboot服務。須要調整一下docker容器的啓動參數,纔可使用jmap等工具。jmap命令須要使用Linux的Capability的PTRACE_ATTACH權限。而Docker自1.10在默認的seccomp配置文件中禁用了PTRACE_ATTACH。目前使用的Docker version是17.04.0-ce。支持的Capability列表能夠詳看runtime-privilege-and-linux-capabilities。node
調整Capability的方式也比較方便。能夠以下直接在運行參數後面加 cap_add,cap-droplinux
$docker run --cap-add=ALL --cap-drop=MKNOD ...1nginx
也能夠在compose中增長:spring
cap_add: - ALL cap_drop: - NET_ADMIN - SYS_ADMIN12345docker
Docker容器中的服務進程api
在排查問題時,通常是先經過JVM性能調優實踐——性能指標分析中的幾個命令來分析基礎的服務器狀態和信息。在微服務架構中,每臺服務器部署着若干運行着服務的容器。在不能經過應用日誌或者問題現象定位問題服務時,須要找到問題容器。性能優化
先經過TOP命令找到耗費關鍵資源的進程。服務器
top - 11:45:13 up 318 days, 20:43, 2 users, load average: 0.15, 0.19, 0.18Tasks: 172 total, 1 running, 171 sleeping, 0 stopped, 0 zombie%Cpu(s): 3.1 us, 1.9 sy, 0.0 ni, 94.7 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 stKiB Mem: 8175392 total, 7868636 used, 306756 free, 204400 buffersKiB Swap: 0 total, 0 used, 0 free. 849564 cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 31399 root 20 0 3585612 806804 12228 S 3.0 9.9 548:20.94 java 6331 root 20 0 3445612 925660 15784 S 2.7 11.3 41:40.29 java 31122 root 20 0 3460712 888776 11568 S 2.0 10.9 484:19.31 java 31147 root 20 0 3288180 811476 12748 S 1.3 9.9 263:44.73 java 8506 root 20 0 3254088 750880 6116 S 1.0 9.2 760:45.19 java 22940 root 20 0 1029012 70584 23396 S 0.7 0.9 0:10.68 node 24550 root 20 0 1229088 43096 8712 S 0.7 0.5 160:15.74 node 7 root 20 0 0 0 0 S 0.3 0.0 606:49.74 rcu_sched 454 sshd 20 0 32792 1924 188 S 0.3 0.0 29:15.40 nginx 13721 root 20 0 25396 1956 1324 S 0.3 0.0 56:29.17 AliYunDunUpdate 16225 root 20 0 3072752 429296 6848 S 0.3 5.3 42:51.01 java 20795 root 20 0 2408848 75344 3960 S 0.3 0.9 2361:22 java 23581 root 20 0 16736 2676 2196 R 0.3 0.0 0:00.01 top 31352 root 20 0 206920 1488 1024 S 0.3 0.0 1:20.48 docker-containe 32000 root 20 0 3061760 403708 6548 S 0.3 4.9 127:01.39 java ... 省略其餘信息1234567891011121314151617181920212223架構
由於Docker容器中還有java進程,因此須要找到具體的父子進程id.用ps -ef命令以下所示。第二列是PID(進程ID),第三列是PPID(父進程ID)。
$ps -ef |grep java root 6310 6293 0 May21 ? 00:00:00 /bin/sh -c java -Dcontainer.host.ip=...root 6331 6310 2 May21 ? 00:41:51 java -Dcontainer.host.ip= -server ... root 8482 8465 0 Apr16 ? 00:00:00 /bin/sh -c java -Dcontainer.host.ip...root 8506 8482 1 Apr16 ? 12:40:53 java -Dcontainer.host.ip= -server...... 省略其餘信息123456
可使用docker inspect查看容器內部信息,找到對應的容器實例的進程信息。以下便可打印當前宿主機的全部運行的容器實例的PID,爲了方便映射,能夠打印對應容器名字,或者容器ID:
## 打印容器pid和容器id$docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.ID}}' | grep "^${PID}"## 打印容器pid和容器name $ docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.Name}}' | grep "^${PID}" 6310, /service-item31369, /gateway-api31094, /service-resource31025, /service-trade30916, /service-user16204, /service-analytics8482, /service-financial... 省略其餘信息12345678910111213
若是要分析最消耗內存的進程,對應的pid= 6331,其所在的docker進程id也即父進程id= 6310,能夠定位出service-item服務最消耗內存資源。定位到服務以後,便可使用docker exec -it service-item ‘/bin/sh’查看容器內部信息。
JVM調優基礎命令
在容器內部,就能夠進一步使用jdk提供的jps、jstack、jstat、jmap等工具來進行jvm問題排查和調優。
jps[options] [hostid]
jps主要用來輸出JVM中運行的進程狀態信息。
-q 輸出類名、Jar名和傳入main方法的參數
-m 輸出傳入main方法的參數
-l 輸出main類或Jar的全限名
-v 輸出傳入JVM的參數
以下查看運行的java進程信息,打印jar名以及運行main方法傳入的參數:
/opt/app # jps -l -m6 /opt/app/app.jar --server.port=8080327 sun.tools.jps.Jps -l -m1234
jstat
jstat - [-t] [-h] [ [] 1
jstat命令能夠用於持續觀察虛擬機內存中各個分區的使用率以及GC的統計數據。vmid是Java虛擬機ID,在Linux/Unix系統取進程ID。
以下面輸出的信息,採樣時間間隔爲1000ms,採樣5次:
/opt/app # jstat -gc 6 1000 5 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 1536.0 1536.0 1233.7 0.0 171520.0 169769.2 249344.0 57018.6 93912.0 91906.8 11264.0 10853.7 6224 47.439 5 3.423 50.8631536.0 1536.0 1233.7 0.0 171520.0 169805.4 249344.0 57018.6 93912.0 91906.8 11264.0 10853.7 6224 47.439 5 3.423 50.8631536.0 1536.0 0.0 1536.0 171520.0 3527.9 249344.0 60347.4 96728.0 94808.1 11520.0 11174.7 6225 47.453 5 3.423 50.8761536.0 1536.0 0.0 1536.0 171520.0 4742.1 249344.0 60347.4 96728.0 94808.1 11520.0 11174.7 6225 47.453 5 3.423 50.8761536.0 1536.0 0.0 1536.0 171520.0 7589.3 249344.0 60347.4 96728.0 94808.1 11520.0 11174.7 6225 47.453 5 3.423 50.87612345678
上述各個列的含義:
S0C、S1C、S0U、S1U:young代的Survivor 0/1區容量(Capacity)和使用量(Used)。0是FromSurvivor,1是ToSurvivor。
EC、EU:Eden區容量和使用量
OC、OU:年老代容量和使用量
MC、MU:元數據區(Metaspace)已經committed的內存空間和使用量
CCSC、CCSU:壓縮Class(Compressed class space)committed的內存空間和使用量。
YGC、YGT:young代GC次數和GC耗時
FGC、FGCT:Full GC次數和Full GC耗時
GCT:GC總耗時
能夠經過分區佔用量上看到,在第2-3秒之間發生了一次YGC。YGC次數+1,而且Survivor from區的內存空間從1233.7->0,Survivor from從0->1536。Eden區也釋放了不少內存空間。其餘變化的空間佔用也有元數據區以及元數據區的壓縮Class區。Compressed class space也是元數據區的一部分,默認是1G,也能夠關閉。具體的jvm8內存分佈再也不詳述。下一篇GC優化會再展開整理下。
若是隻看gc的總統計信息,也能夠用jstat -gcutil vmid查詢:
/opt/app # jstat -gcutil 6 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 100.00 73.76 24.20 98.02 97.00 6225 47.453 5 3.423 50.876 123
jmap [option] pid
jmap能夠用來查看堆內存的使用詳情。內存各個分區能夠經過jmap -heap pid來查看。獲得的輸出以下:
$jmap -heap 6Attaching to process ID 6, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.121-b13using thread-local object allocation.Parallel GC with 2 thread(s)Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 536870912 (512.0MB) NewSize = 44564480 (42.5MB) MaxNewSize = 178782208 (170.5MB) OldSize = 89653248 (85.5MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB)Heap Usage:PS Young GenerationEden Space: capacity = 170393600 (162.5MB) used = 99020080 (94.43290710449219MB) free = 71373520 (68.06709289550781MB) 58.11255821814904% usedFrom Space: capacity = 4194304 (4.0MB) used = 786432 (0.75MB) free = 3407872 (3.25MB) 18.75% usedTo Space: capacity = 4194304 (4.0MB) used = 0 (0.0MB) free = 4194304 (4.0MB) 0.0% usedPS Old Generation capacity = 255328256 (243.5MB) used = 65264912 (62.24147033691406MB) free = 190063344 (181.25852966308594MB) 25.561178783126927% used39531 interned Strings occupying 4599760 bytes.1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
Heap Configuration是堆內存的配置信息。能夠經過運行參數改變。通常經過分析內存分佈和使用狀況以及GC信息,能夠針對不一樣的應用不斷調整到合適的堆內存分區配置。
Heap Usage能夠看堆內存實時的佔用狀況。
使用jmap -histo[:live] pid查看堆內存中的對象的數目,佔用內存(單位是byte),若是帶上live則只統計活對象,以下:
/opt/app/logs # jmap -histo:live 6 | more num #instances #bytes class name---------------------------------------------- 1: 127610 19132008 [C 2: 6460 4074512 [B 3: 37041 3259608 java.lang.reflect.Method 4: 125182 3004368 java.lang.String 5: 86616 2771712 java.util.concurrent.ConcurrentHashMap$Node 6: 70783 2265056 java.util.HashMap$Node 7: 17686 1967496 java.lang.Class 8: 15834 1448440 [Ljava.util.HashMap$Node; 9: 35360 1414400 java.util.LinkedHashMap$Entry 10: 21948 1231624 [Ljava.lang.Object; 11: 9940 1165728 [I 12: 986 1064480 [Ljava.util.concurrent.ConcurrentHashMap$Node; 13: 18685 1046360 java.util.LinkedHashMap 14: 30351 971232 java.lang.ref.WeakReference 15: 50340 805440 java.lang.Object 16: 13490 539600 java.lang.ref.SoftReference 17: 17705 513768 [Ljava.lang.String; 18: 18781 450744 org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource$DefaultCacheKey 19: 20272 434456 [Ljava.lang.Class; 20: 17270 414480 java.beans.MethodRef 21: 23616 377856 java.lang.Integer 22: 11192 358144 java.util.LinkedList 23: 14911 357864 java.util.ArrayList 24: 5700 319200 java.beans.MethodDescriptor12345678910111213141516171819202122232425262728293031
以上示例的排序是按照佔用內存字節數倒序的。class name列中」[C,[B,[I 「是表明char,byte,int.」[L+類名」表明其餘實例。這種寫法跟Class文件的Java的類型表述含義是一致的。加羣617434785裏面有文中的知識點視頻
在進行問題排查時,可使用jmap把進程內存使用狀況dump到文件中,或者dump**.hprof**文件,在本地使用MAT(Eclipse Memory Analyzer)進行分析。也能夠直接用jhat分析查看。
/opt/app# jmap -dump:format=b,file=heapdump 6Dumping heap to /opt/app/logs/heapdump ...Heap dump file created /opt/app# jmap -dump:live,format=b,file=heapLive.hprof 6 123456
jstack [option] pid
jstack能夠用來查看Java進程內的線程堆棧信息。
-l long listings,會打印出額外的鎖信息,在發生死鎖時能夠用jstack -l pid來觀察鎖持有狀況
-m mixed mode,不只會輸出Java堆棧信息,還會輸出C/C++堆棧信息(好比Native方法)
輸出信息以下:
/opt/app # jstack -l 6Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.121-b13 mixed mode):"elasticsearch[Oneg the Prober][listener][T#1]" #221 daemon prio=5 os_prio=0 tid=0x00007fc2a418a800 nid=0x195 waiting on condition [0x00007fc28318d000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000e29f88d0> (a java.util.concurrent.LinkedTransferQueue) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:737) at java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:647) at java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1269) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Locked ownable synchronizers: - None12345678910111213141516171819
在線上問題排查線程鎖信息時,jstack是個很是好用的工具,結合應用日誌能夠迅速定位到問題線程。
Java性能分析工具
對於Java性能調優,之前一直比較好用的工具是JRockit,JProfile(商業)等工具,但隨着JDK7 up40版本以後,jdk會自帶JMC(JavaMissionControl)工具。能夠分析本地應用以及鏈接遠程ip使用。提供了實時分析線程、內存,CPU、GC等信息的可視化界面。從jdk8 up40開始,JMC還提供了在運行時建立JFR記錄(飛行記錄器)。若是是全面分析heap dump,再綜合使用MAT(Eclipse Memory Analyzer)。基本就能夠作不少平常的性能調優以及線上問題排查了。下文簡單介紹一些JMC,基於java version 「1.8.0_60」。
Java Mission Control
在Mac上使用的話,須要先找到jdk中的jmc路徑。
$find /Library/Java -name missioncontrol 12
在我本地的目錄是/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/missioncontrol/。打開jmc以後,經常使用的話能夠留在dock下面。
啓動須要觀察的應用,而後便可在JMC的MBean服務器中觀察到綜合信息以下:
進一步觀察內存以及GC的狀況,視圖以下,能夠觀察到運行時內存各個分區的佔用率。對於「堆直方圖」默認是不開啓的,能夠經過右上角的刷新值來啓用,會影響性能。通常用於排查內存中的大對象的回收問題以及OOM問題時能夠開啓觀察。
對於線程死鎖、線程池資源方面的分析,能夠到線程視圖中觀察活動線程。
Java飛行記錄器(Java Flight Recorder)
Java 8 up40開始,可使用JMC建立JFR記錄。JFR能夠採樣分析收集Java應用程序以及JVM的信息, 它的最小開銷小於2%,不會影響其餘JVM優化。JFR不會記錄全部方法調用,只會探測熱點方法,但不包含Native方法的線程採樣。若是要開啓JFR,須要應用啓動參數中添加:
-XX:+UnlockCommertialFeatures -XX:+FlightRecorder 1
通常仍是建議本地調優和分析時使用。JFR能夠提供固定時間的採樣(默認是1min),以及持續時間的記錄。它們都會dump到一個「.jfr」的文件中。
分析內存信息以下,能夠看到內存使用量,以及基礎的GC配置和統計信息:
詳細分析內存狀況時,須要進一步查看「內存分配」以及「對象統計信息」。其中「對象統計信息」也是默認不開啓的,須要在建立jfr時選擇「啓用」以下:
而後便可看到對象統計信息:
對於熱點方法以及熱點線程的採樣分析圖表也很直觀,在分析一些循環調用時能夠重點關注熱點方法,對於有問題的熱點方法能夠進一步查看「堆棧跟蹤」下的調用鏈:
總結
本文主要介紹了java經常使用的性能優化和排查問題的工具,以及JavaMissionControl工具的一些功能。JMC是官方提供的免費工具,結合MAT,基本能夠處理性能優化的80%場景。JMC還能夠連接遠程ip進行分析。但對於線上問題排查,仍是建議使用jstat,jstack,jmap工具等,結合top、vmstat等快速排查和定位問題。
性能排查通常問題都集中在cpu、內存。前者分析線程,後者分析具體出現問題的內存分區。對於磁盤、IO等資源瓶頸須要綜合不少業務場景進行具體定位。