0%

JVM性能调优笔记

前言

前两天boss说我们整个服务性能还没到瓶颈,让我试试做下性能优化,终于到了正面肛JVM的阶段。
但我还是小白一个,所以开始从头开始记录我的JVM性能调优之路。

工具介绍

虽然JVM调优的可视化功能有很多,比如jconsole、VisualVM、Memory Analyzer等,但在生产环境出现问题的时候,工具的使用会有限制。而且所有工具几乎都依赖jdk的接口和底层的这些命令。而Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo等,接下来一一学习。

jps

命令格式

1
jps [options] [hostid]

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堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。

命令格式

1
jmap [option]  LVMID

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 #instances #bytes class name (module)
-------------------------------------------------------
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是非常有用的。

命令格式

1
jstack [-l][-e] <pid>

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口令

命令格式

1
jinfo <option> <pid>

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:
#Fri Jul 16 05:44:29 UTC 2021
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
-------------------本文结束 感谢阅读-------------------
  • 本文作者: CeaserBorgia
  • 本文链接: https://timegoesby.top/JVM-opt/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!