前言 前两天boss说我们整个服务性能还没到瓶颈,让我试试做下性能优化,终于到了正面肛JVM的阶段。 但我还是小白一个,所以开始从头开始记录我的JVM性能调优之路。
 
工具介绍 虽然JVM调优的可视化功能有很多,比如jconsole、VisualVM、Memory Analyzer等,但在生产环境出现问题的时候,工具的使用会有限制。而且所有工具几乎都依赖jdk的接口和底层的这些命令。而Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo等,接下来一一学习。
jps 命令格式  
option参数 1 2 3 4 -l : 输出主类全名或jar路径 -q : 只输出LVMID -m : 输出JVM启动时传递给main()的参数 -v : 输出JVM启动时显示指定的JVM参数 
 
示例 1 2 3 jps -lvm 1 app.war 748 jdk.jcmd/sun.tools.jps.Jps -lvm -Dapplication.home=/usr/local /openjdk-11 -Xms8m -Djdk.module.main=jdk.jcmd 
 
命令输出格式为:
1 lvmid [ [ classname| JARfilename | "Unknown" ] [ arg* ] [ jvmarg* ] ] 
 
其中的lvmid可以认为是java进程的pid。
jstate 命令格式 1 jstat [option] LVMID [interval] [count] 
 
参数 1 2 3 4 [option] : 操作参数 LVMID : 本地虚拟机进程ID [interval] : 连续输出的时间间隔(ms) [count] : 连续输出的次数 
 
option 参数列举 通过 jstat -options 命令可以打印出有如下options
参数 
解释 
 
 
class 
class loader的行为统计 
 
compiler 
HotSpt JIT编译器行为统计 
 
gc 
垃圾回收堆堆行为统计 
 
gccapacity 
各个垃圾回收代容量(young,old,perm)和他们相应的空间统计 
 
gcutil 
垃圾回收统计概述 
 
gccause 
垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因 
 
gcnew 
新生代行为统计 
 
gcnewcapacity 
新生代与其相应的内存空间的统计 
 
gcold 
年老代和永生代行为统计 
 
gcoldcapacity 
年老代行为统计 
 
gcpermcapacity 
永生代行为统计 
 
printcompilation 
HotSpot编译方法统计 
 
option 参数详解 -class 监视类装载、卸载数量、总空间、耗费的时间
例如:
1 2 3 $ jstat -class 1  Loaded  Bytes  Unloaded  Bytes     Time     17125 31674.6        0     0.0       9.51 
 
结果说明:
1 2 3 4 5 Loaded : 加载class的数量 Bytes : class字节大小(kb) Unloaded : 未加载class的数量 Bytes : 未加载class的字节大小 Time : 加载时间 
 
-compiler 例如:
1 2 3 $ jstat -compiler 1 Compiled Failed Invalid   Time   FailedType FailedMethod    10072      1       0    21.58          1 org/springframework/core/BridgeMethodResolver findBridgedMethod 
 
结果说明
1 2 3 4 5 6 Compiled : 编译数量 Failed : 编译失败数量 Invalid : 无效数量 Time : 编译耗时 FailedType : 失败类型 FailedMethod : 失败方法的全限定名 
 
-gc 垃圾回收堆的行为统计,常用命令  例如:  1 2 3  $ jstat -gc 1  S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT    CGC    CGCT     GCT    8064.0 8064.0  0.0    0.0   64512.0  13619.2   161152.0   43374.8   98428.0 95908.5 13056.0 12029.9     30    0.520   4      0.433   -          -    0.952 
 结果说明C即Capacity 总容量,U即Used 已使用的容量 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 S0C : survivor0区的总容量 S1C : survivor1区的总容量 S0U : survivor0区已使用的容量 S1U : survivor1区已使用的容量 EC : Eden区的总容量 EU : Eden区已使用的容量 OC : Old区的总容量 OU : Old区已使用的容量 PC 当前perm的容量 (KB) PU perm的使用 (KB) YGC : 新生代垃圾回收次数 YGCT : 新生代垃圾回收时间 FGC : 老年代垃圾回收次数 FGCT : 老年代垃圾回收时间 GCT : 垃圾回收总消耗时间 
 
 从这里也可以看出,新生代(survivor0+survivor1+eden)和老年代(old)的比例是 1:2 。 新生代中survivor0:survivor1:eden=1:1:8 
-gccapacity 同-gc,不过还会输出java堆各区域使用到的最大、最小空间 例如:  1 2 3 $ jstat -gccapacity 1 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC   CGC    192.0 1284736.0  80640.0 8064.0 8064.0  64512.0       64.0  2569600.0   161152.0   161152.0      0.0 1144832.0 108764.0      0.0 1048576.0  14336.0     33     4     - 
 结果说明
1 2 3 4 5 6 7 8 NGCMN : 新生代占用的最小空间 NGCMX : 新生代占用的最大空间 OGCMN : 老年代占用的最小空间 OGCMX : 老年代占用的最大空间 OGC:当前年老代的容量 (KB) OC:当前年老代的空间 (KB) PGCMN : perm占用的最小空间 PGCMX : perm占用的最大空间 
 
-gcutil 同-gc,不过输出的是已使用空间占总空间的百分比 例如:  1 2 3 $ jstat -gcutil 1  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT    CGC    CGCT     GCT     0.00  33.22  89.30  28.87  97.20  92.04     33    0.630     4    0.424     -        -    1.054 
-gccause 垃圾收集统计概述,同-gcutil,附加最近两次gc事件的原因。 例如:  1 2 3 $ jstat -gccause 1  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT    CGC    CGCT     GCT    LGCC                 GCC                   0.00  33.22  89.72  28.87  97.20  92.04     33    0.630     4    0.424     -        -    1.054 Allocation Failure   No GC        
 结果说明
1 2 LGCC:最近垃圾回收的原因 GCC:当前垃圾回收的原因 
 
-gcnew 统计新生代的行为 例如:
1 2 3 $ jstat -gcnew 1 S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT   8064.0 8064.0    0.0 2679.0 15  15 4032.0  64512.0  58384.4     33    0.630 
 
结果说明
1 2 3 TT:Tenuring threshold(提升阈值) MTT:最大的tenuring threshold DSS:survivor区域大小 (KB) 
 
-gcnewcapacity 新生代与其相应的内存空间的统计  1 2 3 $ jstat -gcnewcapacity 1 NGCMN      NGCMX       NGC      S0CMX     S0C     S1CMX     S1C       ECMX        EC      YGC   FGC   CGC     192.0  1284736.0    80640.0 128448.0   8064.0 128448.0   8064.0  1027840.0    64512.0    33     4     - 
  结果说明
1 2 3 4 5 NGC:当前年轻代的容量 (KB) S0CMX:最大的S0空间 (KB) S0C:当前S0空间 (KB) ECMX:最大eden空间 (KB) EC:当前eden空间 (KB) 
 
-gcold 统计老年代的行为
1 2 3 $ jstat  -gcold 1    MC       MU      CCSC     CCSU       OC          OU       YGC    FGC    FGCT    CGC    CGCT     GCT    108764.0 105713.3  14336.0  13194.7    161152.0     46531.8     33     4    0.424     -        -    1.054 
 
-gcoldcapacity  统计老年代的大小和空间
1 2 3 $ jstat -gcoldcapacity 1    OGCMN       OGCMX        OGC         OC       YGC   FGC    FGCT    CGC    CGCT     GCT           64.0   2569600.0    161152.0    161152.0    33     4    0.424     -        -    1.054 
 
-gcpermcapacity 永生代行为统计在jdk8以后永生代就没有了!被元空间代替 
-printcompilation hotspot编译方法统计
1 2 3 $  jstat -printcompilation 1 Compiled  Size  Type Method    12689     67    1 java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject  unlinkCancelledWaiters 
 
结果说明:
1 2 3 4 Compiled:被执行的编译任务的数量 Size:方法字节码的字节数 Type:编译类型 Method:编译方法的类名和方法名。类名使用”/” 代替 “.” 作为空间分隔符. 方法名是给出类的方法名. 格式是一致于HotSpot - XX:+PrintComplation 选项 
 
jmap jmap(JVM Memory Map)命令用于生成heap dump文件,如果不使用这个命令,还可以使用-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机出现OOM的时候·自动生成dump文件。 jmap不仅能生成dump文件,还阔以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。
命令格式  
option参数总览 1 2 3 4 5 dump : 生成堆转储快照 finalizerinfo : 显示在F-Queue队列等待Finalizer线程执行finalizer方法的对象( 打印正在等待回收的对象的信息) heap : 显示Java堆详细信息 histo : 显示堆中对象的统计信息 F : 当-dump没有响应时,强制生成dump快照 
 
options详解 -dump 常用格式
1 2 jmap -dump::live,format=b,file=dump.hprof  1 
 
其中format指定输出格式,live指明是活着的对象,file指定文件名,文件名dump.hprof中hprof这个后缀是为了后续可以直接永MAT(Memory Analysis Tool)打开。
-clstats -clstats是-permstat的替代方案,在在JDK8之前,-permstat用来打印类加载器的数据 打印Java堆内存的永久保存区域的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。
-finalizerinfo 1 2 3 4 5 $ jmap -finalizerinfo 1 Debugger attached successfully. Server compiler detected. JVM version is 25.181-b13 Number of objects pending for  finalization: 0 
 
可以看到并没有待回收的对象
-histo[:live] 打印堆的对象统计,包括对象数、内存大小等等 (因为在dump:live前会进行full gc,如果带上live则只统计活对象,因此不加live的堆大小要大于加live堆的大小 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 $ jmap -histo:live 1 num      -------------------------------------------------------    1:        128510       10055296  [B (java.base@11.0.9.1)    2:         92159        2949088  java.util.concurrent.ConcurrentHashMap$Node  (java.base@11.0.9.1)    3:        117899        2829576  java.lang.String (java.base@11.0.9.1)    4:         27272        2399936  java.lang.reflect.Method (java.base@11.0.9.1)    5:         19949        2362928  java.lang.Class (java.base@11.0.9.1)    6:         11348        1639680  [I (java.base@11.0.9.1)    7:         18445        1240488  [Ljava.lang.Object; (java.base@11.0.9.1)    8:         11867        1217440  [Ljava.util.HashMap$Node ; (java.base@11.0.9.1)    9:           726        1208576  [C (java.base@11.0.9.1)   10:         25509        1020360  java.util.LinkedHashMap$Entry  (java.base@11.0.9.1)   11:          1029        1013912  [Ljava.util.concurrent.ConcurrentHashMap$Node ; (java.base@11.0.9.1)   12:         14353         803768  java.util.LinkedHashMap (java.base@11.0.9.1)   13:         24722         791104  java.util.HashMap$Node  (java.base@11.0.9.1)   14:         45425         726800  java.lang.Object (java.base@11.0.9.1)   15:           395         594472  [J (java.base@11.0.9.1)   16:         10960         441680  [Lorg.springframework.util.ConcurrentReferenceHashMap$Reference ;   17:         10960         438400  org.springframework.util.ConcurrentReferenceHashMap$Segment    18:         16932         400152  [Ljava.lang.Class; (java.base@11.0.9.1)   19:         11439         366048  java.lang.ref.ReferenceQueue (java.base@11.0.9.1)   20:         11374         363968  java.util.concurrent.locks.ReentrantLock$NonfairSync  (java.base@11.0.9.1)   21:          8770         265456  [Ljava.lang.String; (java.base@11.0.9.1)   22:          5494         263712  org.springframework.core.ResolvableType   23:         10960         263040  org.springframework.util.ConcurrentReferenceHashMap$ReferenceManager    24:          4351         208848  java.util.HashMap (java.base@11.0.9.1)   25:          1658         198960  org.springframework.boot.loader.jar.JarEntry   26:          8254         198096  org.springframework.core.MethodClassKey   27:          8155         195720  java.util.ArrayList (java.base@11.0.9.1)   28:          7781         186744  org.springframework.data.util.Lazy   29:         11443         183088  java.lang.ref.ReferenceQueue$Lock  (java.base@11.0.9.1)   30:         11152         178432  java.util.concurrent.atomic.AtomicInteger (java.base@11.0.9.1)   31:          3695         177360  java.lang.invoke.MemberName (java.base@11.0.9.1)   32:          4288         171520  java.lang.ref.SoftReference (java.base@11.0.9.1) 
 
其中,xml class name是对象类型,说明如下:
1 2 3 4 5 6 7 8 9 B  byte C  char D  double F  float I  int J  long Z  boolean [  数组,如[I表示int[] [L+类名 其他对象 
 
jstack jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
命令格式  
option 参数总览 1 2 -l  long listing. Prints additional information about locks  除了堆栈外,显示关于锁的附加信息 -e  extended listing. Prints additional information about threads  打印额外的线程信息? 
 
jinfo jinfo(JVM Configuration info)这个命令作用是实时查看和调整虚拟机运行参数。 之前的jps -v口令只能查看到显示指定的参数,如果想要查看未被显示指定的参数的值就要使用jinfo口令
命令格式  
option 参数总览 1 2 3 4 5 6 -flag <name>         to print the value of the named VM flag -flag [+|-]<name>    to enable or disable the named VM flag -flag <name>=<value> to set the named VM flag to the given value -flags               to print VM flags -sysprops            to print Java system properties <no option>          to print both VM flags and system properties 
 
详解 不带参数  输出VM参数和System参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 $ jinfo 1 Java System Properties: awt.toolkit=sun.awt.X11.XToolkit java.specification.version=11 sun.cpu.isalist= sun.jnu.encoding=UTF-8 java.class.path=app.war java.vm.vendor=Oracle Corporation sun.arch.data.model=64 catalina.useNaming=false  java.vendor.url=https\://openjdk.java.net/ user.timezone=Etc/UTC java.vm.specification.version=11 os.name=Linux sun.java.launcher=SUN_STANDARD sun.boot.library.path=/usr/local /openjdk-11/lib sun.java.command=app.war jdk.debug=release sun.cpu.endian=little user.home=/root user.language=en java.specification.vendor=Oracle Corporation java.version.date=2020-11-04 java.home=/usr/local /openjdk-11 file.separator=/ java.vm.compressedOopsMode=Zero based line.separator=\n java.specification.name=Java Platform API Specification java.vm.specification.vendor=Oracle Corporation java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment java.awt.headless=true  java.protocol.handler.pkgs=org.springframework.boot.loader sun.management.compiler=HotSpot 64-Bit Tiered Compilers java.runtime.version=11.0.9.1+1 user.name=root path.separator=\: os.version=4.19.91-22.2.al7.x86_64 java.runtime.name=OpenJDK Runtime Environment file.encoding=UTF-8 sun.net.http.allowRestrictedHeaders=true  spring.beaninfo.ignore=true  sun.nio.ch.bugLevel= java.vm.name=OpenJDK 64-Bit Server VM java.vendor.version=18.9 java.vendor.url.bug=https\://bugreport.java.com/bugreport/ java.io.tmpdir=/tmp catalina.home=/tmp/tomcat.8080.2927585761167491266 com.zaxxer.hikari.pool_number=1 java.version=11.0.9.1 user.dir=/ os.arch=amd64 java.vm.specification.name=Java Virtual Machine Specification PID=1 java.awt.printerjob=sun.print.PSPrinterJob sun.os.patch.level=unknown catalina.base=/tmp/tomcat.8080.2927585761167491266 java.library.path=/usr/java/packages/lib\:/usr/lib64\:/lib64\:/lib\:/usr/lib java.vm.info=mixed mode java.vendor=Oracle Corporation java.vm.version=11.0.9.1+1 sun.io.unicode.encoding=UnicodeLittle java.class.version=55.0 VM Flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=247463936 -XX:MaxHeapSize=3946840064 -XX:MaxNewSize=1315569664 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=82444288 -XX:NonNMethodCodeHeapSize=5825164 -XX:NonProfiledCodeHeapSize=122916538 -XX:OldSize=165019648 -XX:ProfiledCodeHeapSize=122916538 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseSerialGC  VM Arguments: java_command: app.war java_class_path (initial): app.war Launcher Type: SUN_STANDARD 
 
-flag 可以指定某个VM参数,比如MaxNewSize
1 2 $ jinfo -flag MaxNewSize 1 -XX:MaxNewSize=1315569664