JVM参数解析与调优
JVM参数解析与调优
Raymon 2017-06-28 JavaJVM 全称 Java Virtual Machine,Java程序编译之后生成的.class文件就是交由JVM执行,由JVM将.class文件内容翻译成对于系统可识别的机器语言,这就是Java之所以能一次编译,到处运行。
关于JVM配置以及调优是Java程序员进阶必须掌握的,一个优秀的Java程序员可以根据运行环境设置JVM参数,从而达到最优配置,合理充分的利用系统资源,避免生产环境发生一些如OOM的异常或者线程死锁、Java进程CPU消耗过高等问题。
JVM调试工具
jps (JVM Process Status Tool)
--- 输出jvm运行的java进程状态信息
命令格式:
jps [options] [hostId]
hostId缺省值为当前主机 命令指令包括:
-q 不输出类名、Jar名和传入main方法的参数
-m 输出传入main方法的参数
-l 输出main类或Jar的全限名
-v 输出传入JVM的参数
使用如下:
[root@localhost ~]#
[root@localhost ~]# jps -m -l
28353 uyun.bat.monitor.impl.Startup
22852 uyun.bat.datastore.Startup
25799 uyun.bat.event.impl.Startup
19976 /opt/uyun/platform/jetty/start.jar
29320 uyun.bat.report.Startup
jstack
--- 输出具体java进程内线程堆栈信息
jstask应该是比较常用的JVM调试工具,命令格式:
jstack [option] [pid]
命令指令:
-l long listings 打印线程锁信息,发生死锁时可以使用该参数调试
-m mixed mode 不仅输出java堆栈信息,还会输出C/C++堆栈信息
实际应用例子: 查看进程最占CPU的线程堆栈信息
- ps -ef | grep 查找对应进程,或者top命令查看系统使用信息,找出消耗最大的进程,我这里使用的是top命令:
top - 07:38:01 up 3 days, 6:20, 5 users, load average: 15.72, 15.02, 14.14
Tasks: 148 total, 7 running, 141 sleeping, 0 stopped, 0 zombie
%Cpu(s): 71.2 us, 26.1 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 2.7 si, 0.0 st
KiB Mem: 20397888 total, 20124388 used, 273500 free, 0 buffers
KiB Swap: 1081340 total, 1081340 used, 0 free. 2163376 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24152 es 20 0 3323892 492160 28496 S 35.7 2.4 1613:51 java
3247 mysql 20 0 1152924 297932 5836 S 9.9 1.5 418:51.47 mysqld
20009 root 20 0 3688420 1.563g 13132 S 7.9 8.0 653:51.83 java
22852 root 20 0 3450392 546480 12828 S 7.6 2.7 322:33.85 java
5779 root 20 0 3652656 1.114g 4976 S 4.3 5.7 109:57.89 java
28353 root 20 0 3624988 337680 12824 S 3.3 1.7 125:49.11 java
268 root 0 -20 0 0 0 S 2.3 0.0 43:33.35 kworker/0:1H
11539 root 20 0 369916 14108 4020 R 2.0 0.1 0:00.06 python
25799 root 20 0 3356336 475416 12832 S 1.7 2.3 64:56.00 java
1544 root 20 0 247448 27916 1144 S 1.3 0.1 56:24.67 redis-server
11540 root 20 0 131528 5048 3880 S 1.3 0.0 0:00.04 sshd
21497 root 20 0 3306144 313020 12712 S 1.0 1.5 20:59.73 java
1 root 20 0 133816 6772 2084 S 0.7 0.0 40:38.49 systemd
经过top命令查找,最占用CPU的是一个Java进程,进程id为24152,占用内存达到35.7%,经过ps -ef|grep pid查看,这个进程是ElasticSearch进程
- 第二步我们需要查找该进程内最耗费CPU的线程,可以使用ps -Lfp pid, ps -mp pid -o THREAD, top -Hp pid,这里我们用第三个命令,top -Hp 24152
top - 07:44:20 up 3 days, 6:27, 5 users, load average: 19.72, 15.50, 14.42
Threads: 40 total, 1 running, 39 sleeping, 0 stopped, 0 zombie
%Cpu(s): 64.3 us, 32.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 3.7 si, 0.0 st
KiB Mem: 20397888 total, 19894260 used, 503628 free, 0 buffers
KiB Swap: 1081340 total, 1081340 used, 0 free. 1994824 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24937 es 20 0 3319268 485312 21512 R 23.0 2.4 748:12.71 java
24953 es 20 0 3319268 485312 21512 S 4.3 2.4 151:27.67 java
24157 es 20 0 3319268 485312 21512 S 3.0 2.4 142:46.82 java
24459 es 20 0 3319268 485312 21512 S 0.3 2.4 1:58.92 java
24876 es 20 0 3319268 485312 21512 S 0.3 2.4 16:58.66 java
24152 es 20 0 3319268 485312 21512 S 0.0 2.4 0:00.00 java
24154 es 20 0 3319268 485312 21512 S 0.0 2.4 0:02.02 java
24155 es 20 0 3319268 485312 21512 S 0.0 2.4 0:00.00 java
24156 es 20 0 3319268 485312 21512 S 0.0 2.4 42:25.76 java
24158 es 20 0 3319268 485312 21512 S 0.0 2.4 0:31.47 java
这里我们看到最耗费性能的线程pid为24937
- 第三步先获取线程id 24937的十六进制值
[root@localhost ~]# printf "%x\n" 24937
6169
接着使用jstack来输出线程id 24937的堆栈信息,根据线程id的十六进制值grep
jstack 24152 | grep 6169
这里我遇到一个问题,执行jstack命令后系统并没有返回jvm信息,而是给出一个报错信息:
Unable to open socket file: target process not responding or HotSpot VM not loaded
之所以会报这个找不到文件的错误,首先我们得知道jvm运行时会生成一个hsperfdata_$user的目录,Linux下默认是在/tmp,我们也可以通过配置jvm启动参数-Djava.io.tmpdir来指定进程号信息临时文件的存放位置,检查过之后确认 /tmp下有生成目录hsperfdata_es
[root@localhost hsperfdata_es]# pwd
/tmp/hsperfdata_es
[root@localhost hsperfdata_es]# ls
24152
[root@localhost hsperfdata_es]#
那之所以jstack会报错找不到文件,原因是ElasticSearch进程是使用es用户启动的,而我们登录的是root账号,因此访问不到这个文件,切换用户为es后再次使用jstack打印线程堆栈信息:
[root@localhost hsperfdata_es]# su es
[es@localhost hsperfdata_es]$ jstack 24152 | grep 6169
"elasticsearch[Grenade][bulk][T#1]" #49 daemon prio=5 os_prio=0 tid=0x00007f78440b2000 nid=0x6169 runnable [0x00007f7840fa1000]
[es@localhost hsperfdata_es]$
打印出该线程信息,显示该线程是runnable正常运行的就绪状态,经查看详细堆栈信息,应该是es内部创建分片索引的进程,因此占用比较多性能,在对自己环境进程正式排查的时候,可以多进行几次打印,对比多次之间的线程运行情况,正常情况下由于程序运行速度是非常快的,如果发现多次打印对于线程都一直处于同一状态如Runnable,而且堆栈信息也卡在相同的几处地方,就可以考虑看一下对应代码是不是存在死循环或者方法调用缓慢的问题了。
jmap jhat
--- jmap 输出堆内存使用情况 --- jhat java堆内存分析工具
jmap和jhat经常在一起被使用。
jmap -hap pid
用于查看进程堆内存使用情况,包括堆配置参数和各代中堆内存使用情况,如:
whale.server01:/root# jmap -heap 17047
Attaching to process ID 17047, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.77-b03
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 175112192 (167.0MB)
MaxNewSize = 357564416 (341.0MB)
OldSize = 351272960 (335.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 134217728 (128.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 70254592 (67.0MB)
used = 22570248 (21.52466583251953MB)
free = 47684344 (45.47533416748047MB)
32.126366914208255% used
From Space:
capacity = 9961472 (9.5MB)
used = 6859688 (6.541908264160156MB)
free = 3101784 (2.9580917358398438MB)
68.86219225431743% used
To Space:
capacity = 10485760 (10.0MB)
used = 0 (0.0MB)
free = 10485760 (10.0MB)
0.0% used
PS Old Generation
capacity = 184025088 (175.5MB)
used = 99082056 (94.49201202392578MB)
free = 84943032 (81.00798797607422MB)
53.841602292835205% used
24334 interned Strings occupying 2508568 bytes.
使用jmap -histo[:live] pid查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象,如下:
whale.server01:/root# jmap -histo:live 17047 | more
num #instances #bytes class name
----------------------------------------------
1: 77521 8038136 [C
2: 1056 2890752 [J
3: 6362 2595656 [B
4: 5812 1968312 [I
5: 76614 1838736 java.lang.String
6: 19709 1734392 java.lang.reflect.Method
7: 18318 1268024 [Ljava.lang.Object;
8: 10179 1136280 java.lang.Class
9: 33025 1056800 java.util.concurrent.ConcurrentHashMap$Node
10: 12388 594624 org.aspectj.weaver.reflect.ShadowMatchImpl
11: 16901 540832 java.util.HashMap$Node
12: 12304 492160 java.util.LinkedHashMap$Entry
13: 12388 396416 org.aspectj.weaver.patterns.ExposedState
14: 2633 352464 [Ljava.util.HashMap$Node;
15: 11008 352256 java.util.Hashtable$Entry
16: 436 330440 [Ljava.util.concurrent.ConcurrentHashMap$Node;
17: 13383 321192 java.util.ArrayList
18: 9319 298208 java.lang.ref.WeakReference
19: 741 278616 java.lang.Thread
20: 17352 277632 java.lang.Object
21: 5707 228280 java.lang.ref.SoftReference
22: 3612 173376 java.util.HashMap
23: 302 164288 rx.internal.util.unsafe.SpscArrayQueue
24: 5104 163328 java.util.concurrent.locks.ReentrantLock$NonfairSync
25: 2872 160832 java.util.LinkedHashMap
26: 4784 153088 java.lang.ThreadLocal$ThreadLocalMap$Entry
27: 1828 146240 java.lang.reflect.Constructor
28: 1473 139856 [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;
29: 5152 123648 java.beans.MethodRef
30: 3831 119752 [Z
31: 5550 118632 [Ljava.lang.Class;
32: 2003 112168 java.security.Provider$Service
33: 464 107616 [Ljava.util.Hashtable$Entry;
附class对象说明:
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
另一个场景是用jmap dump出进程内存使用情况,然后使用jhat分析。命令如下:
whale.server01:/root# jmap -dump:format=b,file=/tmp/dump.dat 17047
Dumping heap to /tmp/dump.dat ...
Heap dump file created
使用jhat查看:
jhat -port 9998 /tmp/dump.dat
执行完后在浏览器中打开 http://ip:9998 进行查看结果
jstat
--- 输出jvm内存使用情况
jstat 监控系统整体资源使用情况,使用语法:
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
jstat 比较常用到场景是查看实时的垃圾回收统计,通常命令如下:
[root@localhost hsperfdata_root]# jstat -gc 19560 5000
即会每5秒一次显示进程为19560的GC情况:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
17472.0 17472.0 275.9 0.0 139968.0 2271.9 349568.0 21151.3 55808.0 53992.8 6400.0 6044.1 5554 30.602 4 0.809 31.411
17472.0 17472.0 275.9 0.0 139968.0 58303.5 349568.0 21151.3 55808.0 53992.8 6400.0 6044.1 5554 30.602 4 0.809 31.411
17472.0 17472.0 275.9 0.0 139968.0 102692.6 349568.0 21151.3 55808.0 53992.8 6400.0 6044.1 5554 30.602 4 0.809 31.411
17472.0 17472.0 275.9 0.0 139968.0 103396.5 349568.0 21151.3 55808.0 53992.8 6400.0 6044.1 5554 30.602 4 0.809 31.411
17472.0 17472.0 275.9 0.0 139968.0 136708.8 349568.0 21151.3 55808.0 53992.8 6400.0 6044.1 5554 30.602 4 0.809 31.411
17472.0 17472.0 0.0 273.1 139968.0 31730.4 349568.0 21151.3 55808.0 53992.8 6400.0 6044.1 5555 30.605 4 0.809 31.414
17472.0 17472.0 0.0 273.1 139968.0 64034.4 349568.0 21151.3 55808.0 53992.8 6400.0 6044.1 5555 30.605 4 0.809 31.414
可以看出上例中第6次打印GC时,进行了一次YGC,对EU(eden usage)和S0U(Survivor0 usage)的内存空间进行了一次清理。
附上每列说明:
S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
JVM参数
JVM堆内存
整个堆大小 = 年轻代(Young Generation) + 年老代(Old Generation) + 持久代(Perm Area) JVM堆内存用与new创建的对象和数组,栈内存则用于分配基础类型变量和对象的引用,当程序运行到作用域外时,栈内引用将被释放,而失去了引用地址的堆内存里的对象则变为了垃圾,在未知时间被GC回收释放内存;
堆内存构成图:
-
-Xms 初始堆大小 默认物理内存的1/64(小于1GB)空余堆大小小于40%时,JVM就会增大堆直到-Xmx的最大限制
-
-Xmx 最大堆大小 默认物理内存的1/4(小于1GB)空余堆大小大于70%时,JVM就会减少堆直到-Xms的最小限制
我们可以通过将“-Xms”和“-Xmx”设置为相同大小来获得一个固定大小的堆内存。 -Xms和-Xmx实际上是-XX:InitialHeapSize和-XX:MaxHeapSize的缩写。我们也可以直接使用这两个参数,它们所起得效果是一样的
-
-Xmn 年轻代大小
-
-XX:NewSize 设置年轻代初始大小
-
-XX:MaxNewSize 年轻代最大值
-
-XX:PermSize 设置持久代初始值
-
-XX:MaxPermSize 设置持久代最大值
-
-Xss 每个线程堆栈大小 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K,这个参数对影响比较大,需经过严格测试后进行调整
-
-XX:NewRatio 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5,Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。
-
-XX:SurvivorRatio Eden区与Survivor区的大小比值 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-
-XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath 当我们没法为-Xmx(最大堆内存)设置一个合适的大小,那么就有可能面临内存溢出(OutOfMemoryError)的风险,这可能是我们使用JVM时面临的最可怕的猛兽之一导致内存溢出的根本原因需要仔细的定位。通常来说,分析堆内存快照(Heap Dump)是一个很好的定位手段,如果发生内存溢出时没有生成内存快照,特别是对于那种JVM已经崩溃或者错误只出现在顺利运行了数小时甚至数天的生产系统上时,将很难去分析崩溃问题。
幸运的是,我们可以通过设置 -XX:+HeapDumpOnOutOfMemoryError 让JVM在发生内存溢出时自动的生成堆内存快照。有了这个参数,当我们不得不面对内存溢出异常的时候会节约大量的时间。默认情况下,堆内存快照会保存在JVM的启动目录下名为java_pid.hprof 的文件里(在这里就是JVM进程的进程号)。也可以通过设置-XX:HeapDumpPath=来改变默认的堆内存快照生成路径, 可以是相对或者绝对路径。
引用: JVM实用参数内存调优 JVM性能调优监控工具 jstat命令使用
Gitalk 加载中 ...
转载自:https://juejin.cn/post/6844903609436487693