写点什么

☕【JVM 技术探索】重塑虚拟机性能调优计划

发布于: 2021 年 06 月 13 日
☕【JVM技术探索】重塑虚拟机性能调优计划

每日一句


一个人的成功不取决于他的智慧,而是毅力。 

系统性能测试层面

确定性能目标

性能优化不是漫无目的的去优化。结合不同的业务场景以及应用的特点去进行重点优化。

主动的优化需要结合业务未来的发展以及业务目标,参考当前的系统资源和监控数据情况来决定一些性能优化的优先级为了不让性能成为未来业务发展的瓶颈,需要提前准备和布局。比如通过搜索、Cache 等优化对于 DB 的请求。

在实际的情况中,也会存在一些被动的优化。当系统中的一些资源成为瓶颈,已经影响了当前用户、业务的情况。需要技术同学能有结合性能需求进行问题分析和救火能力。

性能指标、资源

对于 Web 应用一般重点关注的性能指标主要是吞吐量、响应时间、QPS、TPS 等、并发用户数等。

系统资源一般指如 CPU、内存、磁盘 IO、网络 IO 等资源。以下说明以 Linux 为例,性能测试中,主要观察的 cpu、内存、磁盘等指标的信息。

CPU 指标分析

CPU 资源一般可以使用 vmstat 来采样(例如每秒采样一次: vmstat 1)查看 CPU 上下文切换。如下:

$ vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st 1  0      0 284628 234036 859100    0    0     1    15    0    1  3  1 96  0  0 0  0      0 284536 234036 859204    0    0     0     0 4952 9461  4  2 95  0  0 0  0      0 284536 234036 859208    0    0     0   200 5081 9768  3  3 94  1  0 0  0      0 284568 234036 859212    0    0     0    16 5126 9757  3  2 96  0  0 0  0      0 284900 234036 859236    0    0     0     0 5431 10230  4  3 94  0  0 1  0      0 285608 234036 859256    0    0     0     0 5325 10005  6  2 92  0  0 0  0      0 285452 234036 859256    0    0     0     0 5037 9653  3  1 96  0  0 0  0      0 285484 234036 859264    0    0     0    60 5068 9599  3  1 96  0  0 0  0      0 285452 234036 859264    0    0     0    36 5163 9825  4  2 94  0  0
复制代码


一般最主要关注的主要是以下指标:

  • us:用户占用 CPU 的百分比

  • sy:系统(内核和中断)占用 CPU 的百分比

  • id:CPU 空闲的百分比

  • in: 系统中断数

  • cs: 每秒上下文切换次数

  • r: 可运行进程数,包括正在运行(Running)和已就绪等待运行(Waiting)的。在负载测试中,其可接受上限通常不超过 CPU 核数的 2 倍。


CPU 使用率通常用 us + sy 来计算,一般大于 80%说明,CPU 资源出现瓶颈。同时也要综合参考 CPU 平均负载 Load Average 信息。


Linux 系统中,一般取运行队列的值 + 处于 task_uninterruptible 状态的进程数(vmstat 输出的 b)。可以通过 top 命令查看 1 分钟、5 分钟和 15 分钟的平均负载值。

一般来说平均负载的 15min 采样大于核数 * 0.7 就要关注和排查一下高负载的原因。


top 命令的 load average 信息如下:

top - 10:41:04 up 11 days, 23:32,  2 usersload average: 0.25, 0.22, 0.18 0.25, 0.22, 0.18
复制代码
内存

vmstat 也输出了内存信息

  • free: 系统可用内存,对于稳定运行的系统,free 可接受的范围通常应该大于物理内存的 20%。

  • so/si : 每秒从内存写入到 SWAP 的数据大小/每秒从 SWAP 读取到内存的数据大小。如果出现频繁的 swap 交换,会影响系统性能,需要一起注意。

  • swpd:系统当前的 swap 空间占用。可以和 so/si 综合分析。如果 swpd 为 0 ,内存资源没有成为瓶颈。

磁盘

对于磁盘,首要关注使用率,IOPS 数据吞吐量,在 Linux 服务区,可以使用 iostat 来获取这些数据。

$ iostat -dxk 1
Linux 4.4.0-63-generic _x86_64
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %utilvda 0.00 2.69 0.07 2.21 1.28 28.96 26.53 0.00 1.85 1.19 1.87 0.31 0.07
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %utilvda 0.00 0.00 2.00 0.00 12.00 0.00 12.00 0.02 10.00 10.00 0.00 10.00 2.00
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %utilvda 0.00 29.00 0.00 2.00 0.00 124.00 124.00 0.00 2.00 0.00 2.00 2.00 0.40
复制代码


  • %util: 衡量 device 使用率的指标。处理 I/O 请求的时间与统计时间的百分比.大于 60%的话,会影响系统性能。

  • r/s, w/s :每秒处理,读、写的请求数量。

  • rkB/s, wkB/s :每秒读/写的数据大小。


JVM 调优基础命令

在容器内部,就可以进一步使用 jdk 提供的 jps、jstack、jstat、jmap 等工具来进行 jvm 问题排查和调优。

jps

jps[options] [hostid]
复制代码

jps 主要用来输出 JVM 中运行的进程状态信息。

  • q 输出类名、Jar 名和传入 main 方法的参数

  • m 输出传入 main 方法的参数

  • l 输出 main 类或 Jar 的全限名

  • v 输出传入 JVM 的参数


如下查看运行的 java 进程信息,打印 jar 名以及运行 main 方法传入的参数:

/opt/app # jps -l -m
6 /opt/app/app.jar --server.port=8080
327 sun.tools.jps.Jps -l -m
复制代码

jstat

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>] 
复制代码

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.863
1536.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.863
1536.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.876
1536.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.876
1536.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.876
复制代码


上述各个列的含义:

  • 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 的总统计信息,也可以用 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  
复制代码

jmap

jmap [option] pid
复制代码

jmap 可以用来查看堆内存的使用详情。内存各个分区可以通过 jmap -heap pid 来查看。得到的输出如下:

$jmap -heap 6
Attaching to process ID 6, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
using 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 Generation
Eden Space:
capacity = 170393600 (162.5MB)
used = 99020080 (94.43290710449219MB)
free = 71373520 (68.06709289550781MB)
58.11255821814904% used
From Space:
capacity = 4194304 (4.0MB)
used = 786432 (0.75MB)
free = 3407872 (3.25MB)
18.75% used
To Space:
capacity = 4194304 (4.0MB)
used = 0 (0.0MB)
free = 4194304 (4.0MB)
0.0% used
PS Old Generation
capacity = 255328256 (243.5MB)
used = 65264912 (62.24147033691406MB)
free = 190063344 (181.25852966308594MB)
25.561178783126927% used
39531 interned Strings occupying 4599760 bytes.
复制代码

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.MethodDescriptor
复制代码

以上示例的排序是按照占用内存字节数倒序的。class name 列中”[C,[B,[I “是代表 char,byte,int.”[L+类名”代表其他实例。这种写法跟 Class 文件的 Java 的类型表述含义是一致的。

在进行问题排查时,可以使用 jmap 把进程内存使用情况 dump 到文件中,或者 dump**.hprof**文件,在本地使用 MAT(Eclipse Memory Analyzer)进行分析。也可以直接用 jhat 分析查看。

/opt/app# jmap -dump:format=b,file=heapdump 6
Dumping heap to /opt/app/logs/heapdump ...
Heap dump file created
/opt/app# jmap -dump:live,format=b,file=heapLive.hprof 6
复制代码

jstack

jstack [option] pid
复制代码

jstack 可以用来查看 Java 进程内的线程堆栈信息。

  • -l long listings,会打印出额外的锁信息,在发生死锁时可以用 jstack -l pid 来观察锁持有情况

  • -m mixed mode,不仅会输出 Java 堆栈信息,还会输出 C/C++堆栈信息(比如 Native 方法)

输出信息如下:

/opt/app # jstack -l 6
Full 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:
- None
复制代码

在线上问题排查线程锁信息时,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


本地的目录是/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

一般还是建议本地调优和分析时使用。JFR 可以提供固定时间的采样(默认是 1min),以及持续时间的记录。它们都会 dump 到一个“.jfr”的文件中。

分析内存信息如下,可以看到内存使用量,以及基础的 GC 配置和统计信息:

详细分析内存情况时,需要进一步查看“内存分配”以及“对象统计信息”。其中“对象统计信息”也是默认不开启的,需要在创建 jfr 时选择“启用”如下:

然后即可看到对象统计信息:

对于热点方法以及热点线程的采样分析图表也很直观,在分析一些循环调用时可以重点关注热点方法,对于有问题的热点方法可以进一步查看“堆栈跟踪”下的调用链:


总结

本文主要介绍了 java 常用的性能优化和排查问题的工具,以及 JavaMissionControl 工具的一些功能。JMC 是官方提供的免费工具,结合 MAT,基本可以处理性能优化的 80%场景。JMC 还可以链接远程 ip 进行分析。但对于线上问题排查,还是建议使用 jstat,jstack,jmap 工具等,结合 top、vmstat 等快速排查和定位问题。

性能排查一般问题都集中在 cpu、内存。前者分析线程,后者分析具体出现问题的内存分区。对于磁盘、IO 等资源瓶颈需要综合很多业务场景进行具体定位。


发布于: 2021 年 06 月 13 日阅读数: 19
用户头像

我们始于迷惘,终于更高水平的迷惘。 2020.03.25 加入

🏆 【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 🤝未来我们希望可以共同进步🤝

评论

发布
暂无评论
☕【JVM技术探索】重塑虚拟机性能调优计划