JVM参数解析与调优

·  阅读 3557
原文链接: www.raymon.xyz

JVM参数解析与调优

Raymon 2017-06-28   Java

JVM 全称 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的线程堆栈信息

  1. 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进程

  1. 第二步我们需要查找该进程内最耗费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

  1. 第三步先获取线程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 加载中 ...

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改