1 前言
本章将主要介绍调优工具及各自的使用方法,最后给出实战案例。
2 工具
2.1 JDK工具
假如你自己手动安装过JDK,那一定不会对下表中的工具感到陌生。比如用的最多的java
。
不同平台,其对应的名字相差无几,只是后缀名不同。比如我自己的MAC OS版本:
在 Linux 中,一般自带了OpenJdk
,一般情况下 JPS
等命令不能用,要么选择去安装 JPS 等插件,要么把 OpenJdk
卸载,去重新安装 Oracle 的 JDK,我推荐后者。
PS:本机JDK版本:
1.8.0_181
2.1.1 jps
- 作用:列出当前机器上正在运行的虚拟机进程。
- 参数:
-q
:仅显示进程-m
:输出主函数传入的参数-l
:输出应用程序主类完整package
名称或jar
完整名称-v
:列出jvm
参数,如:-Xms1024m -Xmx1024m
是启动IDEA
指定的 jvm 参数1892 -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=512m -XX:+IgnoreUnrecognizedVMOptions -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=50 -XX:CICompilerCount=2 -XX:+HeapDumpOnOutOfMemoryError
2.1.2 jstat
- 作用:用于监视虚拟机各种
运行状态信息
的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载
、内存
、垃圾收集
、JIT 编译
等运行数据。是无GUI
界面时的首选。 - 参数:
-class
:类加载统计。- Loaded:加载class的数量
- Bytes:所占用空间大小
- Unloaded:未加载数量
- Bytes:未加载占用空间
- Time:时间
-compiler
:编译统计- Compiled:编译数量。
- Failed:失败数量
- Invalid:不可用数量
- Time:时间
- FailedType:失败类型
- FailedMethod:失败的方法
-gc
(GC 堆状态)-gccapacity
(各区大小)-gccause
(最近一次 GC 统计和原因)-gcnew
(新区统计)-gcnewcapacity
(新区大小)-gcold
(老区统计)-gcoldcapacity
(老区大小)-gcpermcapacity
(永久区大小)-gcutil
(GC 统计汇总)
比如:jstat -gc <pid>
[zyxelva@MacBook-Pro 15:58:19 ~/Documents/MyGit/JVM]$ jstat -gc 2085
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
10752.0 29184.0 0.0 0.0 476160.0 141011.1 167936.0 60198.8 98496.0 93078.6 13440.0 12512.2 16 0.825 4 0.809 1.634
各字段意义如下表所示:
字段 | 含义 |
---|---|
S0C | survivor0区的总容量 |
S1C | survivor1区的总容量 |
S0U | survivor0区已使用的容量 |
S1U | survivor1区已使用的容量 |
EC | Eden区的总容量 |
EU | Eden区已使用的容量 |
OC | Old区的总容量 |
OU | Old区已使用的容量 |
MC | Metaspace的总容量 |
MU | Metaspace已使用的容量 |
CCSC | 压缩类空间容量 |
CCSU | 压缩类空间已使用的容量 |
YGC | 新生代垃圾回收次数 |
YGCT | 新生代垃圾回收时间 |
FGC | Full GC次数 |
FGCT | Full GC时间 |
GCT | 垃圾回收总消耗时间 |
其他带参数命令的字段意义,可参考java高分局之jstat命令使用.
2.1.3 jmap
- 作用:用于生成
堆转储快照
(一般称为heapdump
或dump 文件
);查询finalize 执行队列
、Java 堆
和永久代
的详细信息,如空间使用率
、当前用的是哪种垃圾收集器
等。 - 参数:
-heap <pid>
:打印 heap 的概要信息,GC 使用的算法,heap的配置及wise heap 的使用情况。-histo:live <pid>
:打印每个 class 的实例数目
,内存占用
,类全名信息
;如果live
子参数加上后,只统计活的
对象数量。-dump <pid>
:生成的堆转储快照。一般这么用:jmap -dump:live,format=b,file=heap.bin <pid>
.
PS:
jmap
有不少功能在Windows
平台下都是受限的,除了生成 dump 文件的-dump
选项和用于查看每个类的实例、空间占用统计的-histo
选项在所有操作系统都提供之外,其余选项都只能在Linux/Solaris
下使用。
本机系统为macOS Catalina 10.15.7
,玩不了这个命令,据百度、Google说,要升级到至少9才能用。我特么……
借用别人的机子来解释下各个字段的意思。
例:
jmap -heap 15756
Heap Configuration: ##堆配置情况,也就是 JVM 参数配置的结果[平常说的 tomcat 配置 JVM 参数,就是在配置这些]
MinHeapFreeRatio = 40 ##最小堆使用比例
MaxHeapFreeRatio = 70 ##最大堆可用比例
MaxHeapSize = 2147483648 (2048.0MB) ##最大堆空间大小
NewSize = 268435456 (256.0MB) ##新生代分配大小
MaxNewSize = 268435456 (256.0MB) ##最大可新生代分配大小
OldSize = 5439488 (5.1875MB) ##老年代大小
NewRatio = 2 ##新生代比例
SurvivorRatio = 8 ##新生代与 suvivor 的比例
PermSize = 134217728 (128.0MB) ##perm区永久代大小
MaxPermSize = 134217728 (128.0MB) ##最大可分配 perm 区,也就是永久代大小
Heap Usage: ##堆使用情况【堆内存实际的使用情况】
New Generation (Eden + 1 Survivor Space): ##新生代(伊甸区 Eden 区 + 幸存区 survior(1+2)空间)
capacity = 241631232 (230.4375MB) ##伊甸区容量
used = 77776272 (74.17323303222656MB) ##已经使用大小
free = 163854960 (156.26426696777344MB) ##剩余容量
32.188004570534986% used ##使用比例
Eden Space: ##伊甸区
capacity = 214827008 (204.875MB) ##伊甸区容量
used = 74442288 (70.99369812011719MB) ##伊甸区使用
free = 140384720 (133.8813018798828MB) ##伊甸区当前剩余容量
34.65220164496263% used ##伊甸区使用情况
From Space: ##survior1 区
capacity = 26804224 (25.5625MB) ##survior1 区容量
used = 3333984 (3.179534912109375MB) ##surviror1 区已使用情况
free = 23470240 (22.382965087890625MB) ##surviror1 区剩余容量
12.43827838477995% used ##survior1 区使用比例
To Space: ##survior2 区
capacity = 26804224 (25.5625MB) ##survior2 区容量
used = 0 (0.0MB) ##survior2 区已使用情况
free = 26804224 (25.5625MB) ##survior2 区剩余容量
0.0% used ## survior2 区使用比例
PS Old Generation: ##老年代使用情况
capacity = 1879048192 (1792.0MB) ##老年代容量
used = 30847928 (29.41887664794922MB) ##老年代已使用容量
free = 1848200264 (1762.5811233520508MB) ##老年代剩余容量
1.6416783843721663% used ##老年代使用比例
2.1.4 jhat
- 作用:分析
jmap
生成的dump
。 - 参数:
常和jmap
配合,命令jhat [options] <heap-dump-file-name>
,比如:利用jmap
生成了文件名为heapDump
的dump
日志,
HollisMacBook-Air:apaas hollis$ jhat heapDump
Reading from heapDump...
Dump file created Thu Jan 21 18:59:51 CST 2016
Snapshot read, resolving...
Resolving 341297 objects...
Chasing references, expect 68 dots....................................................................
Eliminating duplicate references....................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
使用jhat命令
,就启动了一个http服务
,端口是7000
,然后在访问http://localhost:7000/
。
2.1.5 jstack
- 作用:用于生成虚拟机当前时刻的线程快照。
- 参数:
线程快照就是当前虚拟机内每一条线程正在执行的
方法堆栈的集合
,生成线程快照的主要目的是定位线程出现长时间停顿的原因
,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。一般来说jstack
主要是用来排查是否有死锁的情况
。2.1.6 jinfo
- 作用:实时查看和调整虚拟机运行参数。
- 参数:
- 生产服务器推荐开启:
-XX:-HeapDumpOnOutOfMemoryError
:默认关闭,建议开启,在java.lang.OutOfMemoryError
异常出现时,输出一个dump 文件
,记录当时的堆内存快照。-XX:HeapDumpPath=./java_pid<pid>.hprof
:用来设置堆内存快照的存储文件路径,默认是java进程启动位置。- 调优之前开启、调优之后关闭:
-XX:+PrintGC
:调试跟踪之打印简单的 GC 信息参数-XX:+PrintGCDetails
,+XX:+PrintGCTimeStamps
:打印详细的 GC 信息-Xlogger:logpath
:设置 gc 的日志路,如:-Xlogger:log/gc.log
, 将gc.log
的路径设置到当前目录的 log 目录下
。将 GC 的日志独立写入日志文件,将 GC 日志与系统业务日志进行了分离,方便开发人员进行追踪分析。- 考虑使用:
-XX:+PrintHeapAtGC
:打印推信息,获取 Heap 在每次垃圾回收前后的使用状况。-XX:+TraceClassLoading
:在系统控制台信息中看到 class 加载的过程和具体的 class 信息,可用以分析类的加载顺序以及是否可进行精简操作。-XX:+DisableExplicitGC
:禁止在运行期显式地调用System.gc()
2.2 可视化工具
2.2.1 Jconsole
“A JMX-compliant graphical tool for monitoring a Java virtual machine. It can monitor both local and remote JVMs. It can also monitor and manage an application.”
意思很直白,就是一个JMX编译而成的监控JVM的图形化工具
。
举例:先启动一个Java应用,我这里启动了一个Eureka注册中心,单节点。然后找到Java安装目录下的bin
目录,双击jconsole
,默认会打开新建连接
页面,选中刚刚启动的Eureka
即可,界面如下:
通过切换tab,可以查看你所需要的东西,比如
内存:
线程:
类:
VM概要:
2.2.2 jvisualvm
启动方式同上。
一般来说,这个工具是本机调试用
,一般生产上来说,你一般是用不了的(除非启用远程连接)。
监视:
其中,
堆dump
类似于jmap
。线程:
其中,
线程Dump
类似于jstack
工具中的栈跟踪。抽样器:
profiler:
2.3 Arthas
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
2.3.1 安装
参见官网文档.
本机采用手动下载jar包和启动文件
as.sh
方式。[zyxelva@172-104-123-96 10:56:11 ~/Documents/Arthas]$ ./as.sh Arthas script version: 3.6.9 [INFO] JAVA_HOME: /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER. * [1]: 1219 com.taeyeon.adminclient.AdminServerApplication [2]: 2196 org.jetbrains.jps.cmdline.Launcher [3]: 1149 [4]: 1181 com.taeyeon.eureka.SpringCloudEurekaApplication 4 Arthas home: /Users/zyxelva/.arthas/lib/3.6.9/arthas Calculating attach execution time... Attaching to 1181 using version /Users/zyxelva/.arthas/lib/3.6.9/arthas... real 0m1.963s user 0m0.399s sys 0m0.049s Attach success. telnet connecting to arthas server... current timestamp is 1689044193 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. ,---. ,------. ,--------.,--. ,--. ,---. ,---. / O \ | .--. ''--. .--'| '--' | / O \ ' .-' | .-. || '--'.' | | | .--. || .-. |`. `-. | | | || |\ \ | | | | | || | | |.-' | `--' `--'`--' '--' `--' `--' `--'`--' `--'`-----' wiki https://arthas.aliyun.com/doc tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html version 3.6.9 main_class com.taeyeon.eureka.SpringCloudEurekaApplication pid 1181 time 2023-07-11 10:56:32 [arthas@1181]$
启动后,首先会让你选择attach
一个进程,这里同样监控eureka
实例,即pid
为1181
的那个。接下来通过介绍各个命令参数来使用arthas。
如果想在浏览器使用命令玩转arthas,也可用在启动后,通过访问
http://127.0.0.1:8563/
,使用Web Console
。也可以填入 IP,远程连接
其它机器上的 arthas。
2.3.2 入门
2.3.2.1 dashboard
输入dashboard
,按回车/enter
,会展示当前attach
进程的信息,按ctrl+c可以中断执行。
当运行在
Ali-tomcat
时,会显示当前tomcat
的实时信息,如 HTTP 请求的 qps, rt, 错误数, 线程池信息等等。
更多详情,可参阅dashboard官方文档.
2.3.2.2 Thread
这个命令和 jstack
很相似,但是功能更加强大,主要是查看当前 JVM 的线程堆栈信息
,同时可以结合使用 thread –b
来进行死锁的排查
。
参数解释:
-n
:指定最忙的前 n 个线程并打印堆栈-b
:找出阻塞当前线程的线程-i
:指定 cpu 占比统计的采样间隔,单位为毫秒
例1:查看当前最忙的前 N 个线程并打印堆栈:
$ thread -n 3
"C1 CompilerThread0" [Internal] cpuUsage=1.63% deltaTime=3ms time=1170ms
"arthas-command-execute" Id=23 cpuUsage=0.11% deltaTime=0ms time=401ms RUNNABLE
at java.management@11.0.7/sun.management.ThreadImpl.dumpThreads0(Native Method)
at java.management@11.0.7/sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:466)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:199)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:122)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)
at java.base@11.0.7/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base@11.0.7/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base@11.0.7/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base@11.0.7/java.lang.Thread.run(Thread.java:834)
"VM Periodic Task Thread" [Internal] cpuUsage=0.07% deltaTime=0ms time=584ms
例2:当没有参数时,显示第一页线程的信息:
[arthas@1181]$ thread
Threads Total: 65, NEW: 0, RUNNABLE: 25, BLOCKED: 0, WAITING: 19, TIMED_WAITING: 11, TERMINATED: 0, Internal threads: 10
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTE DAEMON
-1 C1 CompilerThread2 - -1 - 1.16 0.002 0:8.016 false true
130 arthas-command-execute system 5 RUNNABLE 0.47 0.000 0:0.024 false true
54 Eureka-CacheFillTimer main 5 TIMED_WAIT 0.1 0.000 0:0.244 false true
-1 VM Periodic Task Thread - -1 - 0.08 0.000 0:6.228 false true
42 Catalina-utility-2 main 1 TIMED_WAIT 0.05 0.000 0:0.843 false false
-1 C2 CompilerThread1 - -1 - 0.05 0.000 0:0.235 false true
-1 C2 CompilerThread0 - -1 - 0.02 0.000 0:0.238 false true
41 Catalina-utility-1 main 1 WAITING 0.02 0.000 0:0.864 false false
2 Reference Handler system 10 WAITING 0.0 0.000 0:0.293 false true
3 Finalizer system 8 WAITING 0.0 0.000 0:0.031 false true
4 Signal Dispatcher system 9 RUNNABLE 0.0 0.000 0:0.000 false true
13 RMI TCP Accept-0 system 5 RUNNABLE 0.0 0.000 0:0.054 false true
14 Attach Listener system 9 RUNNABLE 0.0 0.000 0:0.886 false true
16 RMI Scheduler(0) system 5 WAITING 0.0 0.000 0:0.011 false true
106 arthas-timer system 9 WAITING 0.0 0.000 0:0.000 false true
109 arthas-NettyHttpTelnetBootstrap- system 5 RUNNABLE 0.0 0.000 0:0.029 false true
110 arthas-NettyWebsocketTtyBootstra system 5 RUNNABLE 0.0 0.000 0:0.018 false true
111 arthas-NettyWebsocketTtyBootstra system 5 RUNNABLE 0.0 0.000 0:0.002 false true
112 arthas-shell-server system 9 TIMED_WAIT 0.0 0.000 0:0.008 false true
113 arthas-session-manager system 9 TIMED_WAIT 0.0 0.000 0:0.004 false true
114 arthas-UserStat system 9 WAITING 0.0 0.000 0:0.000 false true
116 arthas-NettyHttpTelnetBootstrap- system 5 RUNNABLE 0.0 0.000 0:0.084 false true
117 arthas-NettyWebsocketTtyBootstra system 5 RUNNABLE 0.0 0.000 0:0.106 false true
118 arthas-TermServer-1-1 system 5 RUNNABLE 0.0 0.000 0:0.004 false true
119 arthas-NettyWebsocketTtyBootstra system 5 RUNNABLE 0.0 0.000 0:0.009 false true
120 arthas-TermServer-1-2 system 5 RUNNABLE 0.0 0.000 0:0.011 false true
121 arthas-NettyWebsocketTtyBootstra system 5 RUNNABLE 0.0 0.000 0:0.007 false true
122 arthas-NettyWebsocketTtyBootstra system 5 RUNNABLE 0.0 0.000 0:0.009 false true
123 arthas-TermServer-1-3 system 5 RUNNABLE 0.0 0.000 0:0.003 false true
124 arthas-NettyWebsocketTtyBootstra system 5 RUNNABLE 0.0 0.000 0:0.008 false true
[arthas@1181]$
更多介绍,参阅thread官方文档.
2.3.2.3 jvm
查看当前 JVM 信息.
[arthas@1181]$ jvm
RUNTIME
--------------------------------------------------------------------------------------------------------------------------------------
MACHINE-NAME 1181@172-104-123-96.ip.linodeusercontent.com
JVM-START-TIME 2023-07-11 09:27:13
MANAGEMENT-SPEC-VERSION 1.2
SPEC-NAME Java Virtual Machine Specification
SPEC-VENDOR Oracle Corporation
SPEC-VERSION 1.8
VM-NAME Java HotSpot(TM) 64-Bit Server VM
VM-VENDOR Oracle Corporation
VM-VERSION 25.181-b13
INPUT-ARGUMENTS -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:50075,suspend=y,server=n
-XX:TieredStopAtLevel=1
-Xverify:none
-Dspring.output.ansi.enabled=always
-javaagent:/Users/zyxelva/Library/Caches/JetBrains/IntelliJIdea2021.3/captureAgent/debugger-ag
ent.jar
-Dcom.sun.management.jmxremote
-Dspring.jmx.enabled=true
-Dspring.liveBeansView.mbeanDomain
-Dspring.application.admin.enabled=true
-Dfile.encoding=UTF-8
CLASS-PATH /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/charsets.jar:/Library
/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/Java
VirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirt
ualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMach
ines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/
jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.
0_181.jdk/Contents/Home/jre/lib/ext/localedata.jar:此处省略一万字符
BOOT-CLASS-PATH /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar:/Librar
y/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVir
tualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMa
chines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.
8.0_181.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/C
ontents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/
Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/clas
ses:/Users/zyxelva/Library/Caches/JetBrains/IntelliJIdea2021.3/captureAgent/debugger-agent.jar
LIBRARY-PATH /Users/zyxelva/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensio
ns:/System/Library/Java/Extensions:/usr/lib/java:.
--------------------------------------------------------------------------------------------------------------------------------------
CLASS-LOADING
--------------------------------------------------------------------------------------------------------------------------------------
LOADED-CLASS-COUNT 30462
TOTAL-LOADED-CLASS-COUNT 30485
UNLOADED-CLASS-COUNT 23
IS-VERBOSE false
--------------------------------------------------------------------------------------------------------------------------------------
COMPILATION
--------------------------------------------------------------------------------------------------------------------------------------
NAME HotSpot 64-Bit Tiered Compilers
TOTAL-COMPILE-TIME 23630
[time (ms)]
--------------------------------------------------------------------------------------------------------------------------------------
GARBAGE-COLLECTORS
--------------------------------------------------------------------------------------------------------------------------------------
PS Scavenge name : PS Scavenge
[count/time (ms)] collectionCount : 25
collectionTime : 984
PS MarkSweep name : PS MarkSweep
[count/time (ms)] collectionCount : 12
collectionTime : 2333
--------------------------------------------------------------------------------------------------------------------------------------
MEMORY-MANAGERS
--------------------------------------------------------------------------------------------------------------------------------------
CodeCacheManager Code Cache
Metaspace Manager Metaspace
Compressed Class Space
PS Scavenge PS Eden Space
PS Survivor Space
PS MarkSweep PS Eden Space
PS Survivor Space
PS Old Gen
--------------------------------------------------------------------------------------------------------------------------------------
MEMORY
--------------------------------------------------------------------------------------------------------------------------------------
HEAP-MEMORY-USAGE init : 134217728(128.0 MiB)
[memory in bytes] used : 277785000(264.9 MiB)
committed : 1059061760(1010.0 MiB)
max : 1908932608(1.8 GiB)
NO-HEAP-MEMORY-USAGE init : 2555904(2.4 MiB)
[memory in bytes] used : 198825896(189.6 MiB)
committed : 208535552(198.9 MiB)
max : -1(-1 B)
PENDING-FINALIZE-COUNT 0
--------------------------------------------------------------------------------------------------------------------------------------
OPERATING-SYSTEM
--------------------------------------------------------------------------------------------------------------------------------------
OS Mac OS X
ARCH x86_64
PROCESSORS-COUNT 4
LOAD-AVERAGE 2.37109375
VERSION 10.15.7
--------------------------------------------------------------------------------------------------------------------------------------
THREAD
--------------------------------------------------------------------------------------------------------------------------------------
COUNT 55
DAEMON-COUNT 51
PEAK-COUNT 56
STARTED-COUNT 120
DEADLOCK-COUNT 0
--------------------------------------------------------------------------------------------------------------------------------------
FILE-DESCRIPTOR
--------------------------------------------------------------------------------------------------------------------------------------
MAX-FILE-DESCRIPTOR-COUNT 10240
OPEN-FILE-DESCRIPTOR-COUNT 273
2.3.2.4 Jad
反编译指定已加载类的源码。
/**
[arthas@1181]$ jad com.taeyeon.eureka.SpringCloudEurekaApplication
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@1df82230
Location:
/Users/zyxelva/Documents/xxx/xxx/spring-boot-interview-eureka/target/classes/
*/
/*
* Decompiled with CFR.
*/
package com.taeyeon.eureka;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class SpringCloudEurekaApplication {
private static final Logger log = LoggerFactory.getLogger(SpringCloudEurekaApplication.class);
public static void main(String[] args) {
/*18*/ log.info("------------------------SpringCloudEurekaApplication 开始启动------------------------");
/*19*/ SpringApplication.run(SpringCloudEurekaApplication.class, args);
/*20*/ log.info("------------------------SpringCloudEurekaApplication 启动成功------------------------");
}
}
//Affect(row-cnt:2) cost in 1454 ms.
2.3.3 命令汇总
命令 | 介绍 |
---|---|
dashboard | 当前系统的实时数据面板 |
thread | 查看当前 JVM 的线程堆栈信息 |
watch | 方法执行数据观测 |
trace | 方法内部调用路径,并输出方法路径上的每个节点上耗时 |
stack | 输出当前方法被调用的调用路径 |
tt | 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 |
monitor | 方法执行监控 |
jvm | 查看当前 JVM 信息 |
vmoption | 查看,更新 JVM 诊断相关的参数 |
sc | 查看 JVM 已加载的类信息 |
sm | 查看已加载类的方法信息 |
jad | 反编译指定已加载类的源码 |
classloader | 查看 classloader 的继承树,urls,类加载信息 |
heapdump | 类似 jmap 命令的 heap dump 功能 |
更多命令使用,参阅命令列表.
3 调优
3.1 内存优化
3.1.1 AB压测
压测工具AB
(Apache Bench)测试工具是 Apache 提供的一款测试工具,具有简单易上手的特点,在测试 Web 服务时非常实用。
ab 一般都是在 Linux 上用。安装非常简单,只需要在 Linux 系统中输入 yum -y install httpd-tools
命令,就可以了。
本机macOS自带了,所以,怎么安装,请自行Google。
安装成功后,输入 ab 命令,可以看到以下信息:
[zyxelva@Taeyeons-MacBook-Pro 09:58:45 ~]$ ab
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
-n requests Number of requests to perform
-c concurrency Number of multiple requests to make at a time
-t timelimit Seconds to max. to spend on benchmarking
This implies -n 50000
-s timeout Seconds to max. wait for each response
Default is 30 seconds
-b windowsize Size of TCP send/receive buffer, in bytes
-B address Address to bind to when making outgoing connections
-p postfile File containing data to POST. Remember also to set -T
-u putfile File containing data to PUT. Remember also to set -T
-T content-type Content-type header to use for POST/PUT data, eg.
'application/x-www-form-urlencoded'
Default is 'text/plain'
-v verbosity How much troubleshooting info to print
-w Print out results in HTML tables
-i Use HEAD instead of GET
-x attributes String to insert as table attributes
-y attributes String to insert as tr attributes
-z attributes String to insert as td or th attributes
-C attribute Add cookie, eg. 'Apache=1234'. (repeatable)
-H attribute Add Arbitrary header line, eg. 'Accept-Encoding: gzip'
Inserted after all normal header lines. (repeatable)
-A attribute Add Basic WWW Authentication, the attributes
are a colon separated username and password.
-P attribute Add Basic Proxy Authentication, the attributes
are a colon separated username and password.
-X proxy:port Proxyserver and port number to use
-V Print version number and exit
-k Use HTTP KeepAlive feature
-d Do not show percentiles served table.
-S Do not show confidence estimators and warnings.
-q Do not show progress when doing more than 150 requests
-l Accept variable document length (use this for dynamic pages)
-g filename Output collected data to gnuplot format file.
-e filename Output CSV file with percentages served
-r Don't exit on socket receive errors.
-m method Method name
-h Display usage information (this message)
-I Disable TLS Server Name Indication (SNI) extension
-Z ciphersuite Specify SSL/TLS cipher suite (See openssl ciphers)
-f protocol Specify SSL/TLS protocol
(TLS1, TLS1.1, TLS1.2 or ALL)
-E certfile Specify optional client certificate chain and private key
- 测试Get请求:
ab -c 10 -n 100 http://www.test.api.com/test/login?userName=test&password=test
- 测试Post请求:
其中,ab -n 100 -c 10 -p 'post.txt' -T 'application/x-www-form-urlencoded' 'http://test.api.com/test/register'
post.txt
存放了该接口的参数,格式为:usernanme=test&password=test&sex=1
。 - 参数的含义:
-n
:总请求次数(最小默认为 1);-c
:并发次数(最小默认为 1 且不能大于总请求次数,例如:10 个请求,10 个并发,实际就是 1 人请求 1 次);-p
:post 参数文档路径(-p
和-T
参数要配合使用);-T
:header 头内容类型(此处切记是大写英文字母 T);
- 性能指标参考:
Requests per second
:吞吐率,指某个并发用户数下单位时间内处理的请求数;Time per request
:上面的是用户平均请求等待时间,指处理完成所有请求数所花费的时间 /(总请求数 / 并发用户数);Time per request
:下面的是服务器平均请求处理时间,指处理完成所有请求数所花费的时间 / 总请求数;Percentage of the requests served within a certain time
:每秒请求时间分布情况,指在整个请求中,每个请求的时间长度的分布情况,例如有 50% 的请求 响应在 8ms 内,66% 的请求响应在 10ms 内,说明有 16% 的请求在 8ms~10ms 之间。
3.1.2 JVM 内存分配的调优案例
本人的老苹果
快8年了,扛不住10万的并发了,这里只是抛砖引玉,模拟一万并发,各位看官不要唏嘘mac的性能哈。
3.1.2.1 测试接口
以下代码为待压测的接口:
@RequestMapping(value = "/neo")
@RestController
public class ApiController {
@GetMapping("/heap")
public String test() {
List<Byte[]> list = new ArrayList<Byte[]>();
Byte[] b = new Byte[1024 * 1024];
list.add(b);
return "success";
}
}
3.1.2.2 样例
对应用服务进行压力测试,模拟不同并发用户数下的服务的响应情况:
- 10 个并发用户/1万请求量(总):
ab -c 10 -n 10000 http://127.0.0.1:8088/neo/heap
- 100 个并发用户/1万请求量(总):
ab -c 100 -n 10000 http://127.0.0.1:8088/neo/heap
- 1000 个并发用户/1万请求量(总):
ab -c 1000 -n 10000 http://127.0.0.1:8088/neo/heap
3.1.2.3 监控
- GC 监控
其中,jstat -gc 1816 5000 20 | awk '{print $13,$14,$15,$16,$17}'
1816
为应用进程ID。[zyxelva@Taeyeons-MacBook-Pro 10:28:15 ~]$ jps 1816 spring-boot-interview-api-gateway.jar 1755 Jps
- 堆监控
在默认不配置 JVM 堆内存大小的情况下,JVM 根据默认值
来配置当前内存大小。 我们可以通过以下命令来查看堆内存配置的默认值:
可以看出,这台机器上启动的 JVM 默认[zyxelva@Taeyeons-MacBook-Pro 10:30:12 ~]$ java -XX:+PrintFlagsFinal -version | grep HeapSize uintx ErgoHeapSizeLimit = 0 {product} uintx HeapSizePerGCThread = 87241520 {product} uintx InitialHeapSize := 134217728 {product} uintx LargePageHeapSizeThreshold = 134217728 {product} uintx MaxHeapSize := 2147483648 {product} java version "1.8.0_181" Java(TM) SE Runtime Environment (build 1.8.0_181-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
最大堆内存
大约为 2GB,初始化大小大约为 130MB。
3.1.2.4 压测结果
3.1.2.4.1 10 个并发用户/1万请求量(总)
[zyxelva@Taeyeons-MacBook-Pro 10:43:36 ~]$ ab -c 10 -n 10000 http://127.0.0.1:8088/neo/heap
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software:
Server Hostname: 127.0.0.1
Server Port: 8088
Document Path: /neo/heap
Document Length: 7 bytes
Concurrency Level: 10
Time taken for tests: 14.078 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1390000 bytes
HTML transferred: 70000 bytes
Requests per second: 710.30 [#/sec] (mean)
Time per request: 14.078 [ms] (mean)
Time per request: 1.408 [ms] (mean, across all concurrent requests)
Transfer rate: 96.42 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 4 4.3 4 271
Processing: 2 9 13.5 8 390
Waiting: 0 7 13.0 6 390
Total: 3 13 13.9 12 390
Percentage of the requests served within a certain time (ms)
50% 12
66% 13
75% 14
80% 15
90% 16
95% 19
98% 25
99% 39
100% 390 (longest request)
GC情况:
[zyxelva@172-104-123-96 10:47:54 ~]$ jstat -gc 1816 5000 20 | awk '{print $13,$14,$15,$16,$17 }'
YGC YGCT FGC FGCT GCT
79 0.625 3 0.287 0.912
80 0.625 3 0.287 0.912
111 0.890 3 0.287 1.177
139 1.100 3 0.287 1.387
141 1.119 3 0.287 1.406
141 1.119 3 0.287 1.406
141 1.119 3 0.287 1.406
141 1.119 3 0.287 1.406
141 1.119 3 0.287 1.406
141 1.119 3 0.287 1.406
141 1.119 3 0.287 1.406
141 1.119 3 0.287 1.406
测试结果显示:
- 用户的吞吐量大于在710.3/秒左右
- JVM服务器平均请求处理时间1.408ms左右
- JVM服务器发生了141次YGC,耗时1.119秒,还有3次FGC,0.287秒左右,加在一起GC耗时1.406秒.
3.1.2.4.2 100 个并发用户/1万请求量(总)
[zyxelva@Taeyeons-MacBook-Pro 10:49:49 ~]$ ab -c 100 -n 10000 http://127.0.0.1:8088/neo/heap This is ApacheBench, Version 2.3 <$Revision: 1879490 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Server Hostname: 127.0.0.1 Server Port: 8088 Document Path: /neo/heap Document Length: 7 bytes Concurrency Level: 100 Time taken for tests: 10.264 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1390000 bytes HTML transferred: 70000 bytes Requests per second: 974.30 [#/sec] (mean) Time per request: 102.638 [ms] (mean) Time per request: 1.026 [ms] (mean, across all concurrent requests) Transfer rate: 132.25 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 36 18.4 37 194 Processing: 4 66 22.7 63 233 Waiting: 1 47 19.9 46 208 Total: 6 102 29.8 104 289 Percentage of the requests served within a certain time (ms) 50% 104 66% 113 75% 118 80% 120 90% 130 95% 144 98% 161 99% 212 100% 289 (longest request)
GC情况:
[zyxelva@172-104-123-96 10:49:41 ~]$ jstat -gc 1816 5000 20 | awk '{print $13,$14,$15,$16,$17 }'
YGC YGCT FGC FGCT GCT
79 1.149 6 0.553 1.702
79 1.149 6 0.553 1.702
79 1.149 6 0.553 1.702
97 1.282 6 0.553 1.834
123 1.459 6 0.553 2.011
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
142 1.567 6 0.553 2.119
测试结果显示:
- 用户的吞吐量大于在974.30/秒左右
- JVM服务器平均请求处理时间1.026ms左右
- JVM服务器发生了142多次YGC,耗时1.567秒,还有6次FGC,0.553秒左右,加在一起GC耗时2.12秒.
3.1.2.4.3 1000 个并发用户/1万请求量(总)
[zyxelva@Taeyeons-MacBook-Pro 10:49:49 ~]$ ab -c 1000 -n 10000 http://127.0.0.1:8088/neo/heap
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software:
Server Hostname: 127.0.0.1
Server Port: 8088
Document Path: /neo/heap
Document Length: 7 bytes
Concurrency Level: 1000
Time taken for tests: 8.364 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1390000 bytes
HTML transferred: 70000 bytes
Requests per second: 1262.37 [#/sec] (mean)
Time per request: 78.638 [ms] (mean)
Time per request: 0.786 [ms] (mean, across all concurrent requests)
Transfer rate: 169.25 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.6 1 8
Processing: 1 78 85.0 53 1006
Waiting: 1 77 84.8 52 1005
Total: 1 79 85.0 54 1006
Percentage of the requests served within a certain time (ms)
50% 54
66% 69
75% 83
80% 95
90% 152
95% 260
98% 386
99% 458
100% 1006 (longest request)
3.1.2.4.4 结果分析
- GC 频率:高频的
Full GC
会给系统带来非常大的性能消耗,虽然Minor GC
相对Full GC
来说好了许多,但过多的Minor GC
仍会给系统带来压力。 - 内存:这里的内存指的是
堆内存大小
,堆内存又分为年轻代内存
和老年代内存
。堆内存不足,会增加Minor GC
,影响系统性能。 - 吞吐量:频繁的 GC 将会引起线程的上下文切换,增加系统的性能开销,从而影响每次处理的线程请求,最终导致系统的吞吐量下降。
- 延时:JVM 的 GC 持续时间也会影响到每次请求的响应时间。
3.1.3 调优方案
3.1.3.1 调整方案一
调整堆内存空间减少GC
,通过分析,堆内存基本被用完了,而且存在大量Minor GC
和Full GC
,这意味着我们的堆内存严重不足,这个时候我们需要调大堆内存空间。
# 堆空间加大到 3.0G
java -jar -Xms3000m -Xmx3000m spring-boot-interview-api-gateway.jar
3.1.3.1.1 10 个并发用户/1万请求量(总)
[zyxelva@172-104-123-96 14:43:53 ~]$ ab -c 10 -n 10000 http://127.0.0.1:8088/neo/heap
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software:
Server Hostname: 127.0.0.1
Server Port: 8088
Document Path: /neo/heap
Document Length: 7 bytes
Concurrency Level: 10
Time taken for tests: 10.096 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1390000 bytes
HTML transferred: 70000 bytes
Requests per second: 990.53 [#/sec] (mean)
Time per request: 10.096 [ms] (mean)
Time per request: 1.010 [ms] (mean, across all concurrent requests)
Transfer rate: 134.46 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 3 3.4 3 213
Processing: 1 6 6.3 6 217
Waiting: 0 5 4.0 4 213
Total: 2 10 7.3 9 222
Percentage of the requests served within a certain time (ms)
50% 9
66% 10
75% 11
80% 12
90% 13
95% 14
98% 16
99% 18
100% 222 (longest request)
GC情况:
[zyxelva@172-104-123-96 14:43:48 ~]$ jstat -gc 3757 5000 20 | awk '{print $13,$14,$15,$16,$17 }'
YGC YGCT FGC FGCT GCT
131 1.011 2 0.111 1.122
139 1.049 2 0.111 1.160
161 1.152 2 0.111 1.263
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
173 1.219 2 0.111 1.330
测试结果显示:
- 用户的吞吐量大于在990.53/秒左右
- JVM服务器平均请求处理时间1.010ms左右
- JVM服务器发生了173次YGC,耗时1.219秒,还有2次FGC,0.111秒左右,加在一起GC耗时1.33秒.
3.1.3.1.2 100 个并发用户/1万请求量(总)
[zyxelva@172-104-123-96 14:19:07 ~]$ ab -c 100 -n 10000 http://127.0.0.1:8088/neo/heap This is ApacheBench, Version 2.3 <$Revision: 1879490 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Server Hostname: 127.0.0.1 Server Port: 8088 Document Path: /neo/heap Document Length: 7 bytes Concurrency Level: 100 Time taken for tests: 13.893 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1390000 bytes HTML transferred: 70000 bytes Requests per second: 719.80 [#/sec] (mean) Time per request: 138.927 [ms] (mean) Time per request: 1.389 [ms] (mean, across all concurrent requests) Transfer rate: 97.71 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 44 24.3 46 211 Processing: 4 93 45.9 84 479 Waiting: 1 67 43.6 60 477 Total: 5 137 44.5 130 481 Percentage of the requests served within a certain time (ms) 50% 130 66% 142 75% 150 80% 154 90% 176 95% 200 98% 282 99% 348 100% 481 (longest request)
GC情况:
[zyxelva@172-104-123-96 14:20:18 ~]$ jstat -gc 3757 5000 20 | awk '{print $13,$14,$15,$16,$17 }'
YGC YGCT FGC FGCT GCT
2 0.026 2 0.111 0.137
2 0.026 2 0.111 0.137
20 0.436 2 0.111 0.547
37 0.568 2 0.111 0.679
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
47 0.622 2 0.111 0.733
测试结果显示:
- 用户的吞吐量大于在719.8/秒左右
- JVM服务器平均请求处理时间1.389ms左右
- JVM服务器发生了47多次YGC,耗时1.622秒,还有2次FGC,0.111秒左右,加在一起GC耗时0.733秒.
3.1.3.1.3 1000 个并发用户/1万请求量(总)
3.1.3.2 调整方案二
增大新生代比例,新生代内部比例:8:1:1
java -jar -Xms3000m -Xmx3000m -Xmn2000m -XX:SurvivorRatio=8 spring-boot-interview-api-gateway.jar
3.1.3.2.1 10 个并发用户/1万请求量(总)
[zyxelva@172-104-123-96 15:01:53 ~]$ ab -c 10 -n 10000 http://127.0.0.1:8088/neo/heap
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software:
Server Hostname: 127.0.0.1
Server Port: 8088
Document Path: /neo/heap
Document Length: 7 bytes
Concurrency Level: 10
Time taken for tests: 13.771 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1390000 bytes
HTML transferred: 70000 bytes
Requests per second: 726.15 [#/sec] (mean)
Time per request: 13.771 [ms] (mean)
Time per request: 1.377 [ms] (mean, across all concurrent requests)
Transfer rate: 98.57 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 4 4.4 4 277
Processing: 1 9 15.1 7 281
Waiting: 0 7 15.2 5 277
Total: 2 13 15.3 11 285
Percentage of the requests served within a certain time (ms)
50% 11
66% 12
75% 13
80% 13
90% 15
95% 19
98% 42
99% 73
100% 285 (longest request)
GC情况:
[zyxelva@172-104-123-96 15:03:07 ~]$ jstat -gc 4057 5000 20 | awk '{print $13,$14,$15,$16,$17 }'
YGC YGCT FGC FGCT GCT
24 0.534 2 0.122 0.656
25 0.556 2 0.122 0.678
34 0.615 2 0.122 0.737
42 0.662 2 0.122 0.785
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
44 0.675 2 0.122 0.797
测试结果显示:
- 用户的吞吐量大于在726.15/秒左右
- JVM服务器平均请求处理时间1.377ms左右
- JVM服务器发生了44次YGC,耗时0.675秒,还有2次FGC,0.122秒左右,加在一起GC耗时0.8秒.
3.1.3.2.2 100 个并发用户/1万请求量(总)
[zyxelva@172-104-123-96 15:13:11 ~]$ ab -c 100 -n 10000 http://127.0.0.1:8088/neo/heapThis is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software:
Server Hostname: 127.0.0.1
Server Port: 8088
Document Path: /neo/heap
Document Length: 7 bytes
Concurrency Level: 100
Time taken for tests: 13.431 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1390000 bytes
HTML transferred: 70000 bytes
Requests per second: 744.56 [#/sec] (mean)
Time per request: 134.308 [ms] (mean)
Time per request: 1.343 [ms] (mean, across all concurrent requests)
Transfer rate: 101.07 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 35 18.4 36 119
Processing: 4 98 202.7 58 2008
Waiting: 1 81 203.3 44 1976
Total: 7 133 201.7 98 2059
Percentage of the requests served within a certain time (ms)
50% 98
66% 116
75% 132
80% 140
90% 163
95% 186
98% 787
99% 1667
100% 2059 (longest request)
GC情况:
[zyxelva@172-104-123-96 15:14:38 ~]$ jstat -gc 4057 5000 20 | awk '{print $13,$14,$15,$16,$17 }'
YGC YGCT FGC FGCT GCT
130 10.348 2 0.122 10.470
131 10.348 2 0.122 10.470
136 11.977 3 0.976 12.953
147 12.923 3 0.976 13.900
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
151 13.261 3 0.976 14.237
测试结果显示:
- 用户的吞吐量大于在744.56/秒左右
- JVM服务器平均请求处理时间1.343ms左右
- JVM服务器发生了151次YGC,耗时13.261秒,还有3次FGC,0.976秒左右,加在一起GC耗时14.237秒.
3.1.4 总结
本次测试只能做个参考,让大家理解下平时开发人员是怎样用AB来进行压测的,其他工具还有jmeter;另外,由于本机为笔记本,性能着实有限,实际开发过程中,应该利用单独的测试机,尽可能地避免其他因素干扰。这样做出来的结果才值得参考。
这里给出之前上课时,老师给的结果,他的机子比较牛,测试的是10万请求。仅供参考!
VM参数配置 | 性能指标 | 10 个并发/10万总请求 | 100 个并发/10万总请求 | 1000 个并发/10万总请求 |
默认: 最大堆内存为 480MB, 初始化大小为 32MB。 Eden 区 103m From、To 3~4M OLd =18 | 吞吐量 | 1426/s | 1262/s | 1145/s |
平均处理时间 | 0.7ms | 0.8ms | 0.8ms | |
GC 耗时 | 17s | 33s | 42s | |
最大堆内存为 1.5G, 初始化大小为 1.5G Eden 区 375m From、To 62M OLd =1000M | 吞吐量 | 1205/s | 989/s | 749/s |
平均处理时间 | 0.83ms | 1.01ms | 1.3ms | |
GC 耗时 | 34s | 52s | 75s | |
最大堆内存为 1.5G, 初始化大小为 1.5G Eden 区 800m | 吞吐量 | 1780/s | 1927/s | 1657/s |
平均处理时间 | 0.56ms | 0.51ms | 0.6ms |
一般情况下,高并发业务场景中,需要一个比较大的堆空间
,而默认参数情况下,堆空间不会很大。所以我们有必要进行调整。
但是不要单纯的调整堆的总大小,要调整新生代和老年代的比例,以及 Eden 区还有 From 区、To 区的比例。 所以在我们上述的测试中,调整方案二(不是我的,是老师的方案),得到结果是最好的。在三种测试情况下都能够有非常好的性能指标,同时 GC 耗时相对控制也较好。
- 对于调整方案一,就是单纯地加大堆空间,里面的比例不适合高并发场景,反而导致堆空间变大,没有明显减少 GC 的次数,但是每次 GC 需要检索对象的堆空间更大,所以 GC 耗时更长。
- 而方案二:调整为一个很大的新生代和一个较小的老年代。原因是这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽存放长期存活对象。
由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC,因此我们可以通过增大新生代空间来降低 Minor GC 的频率。详见3.2.2.1节。
3.1.4 推荐策略
3.1.4.1 新生代大小选择
响应时间优先的应用
:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,新生代收集发生的频率也是最小的。同时,减少到达老年代的对象。吞吐量优先的应用
:尽可能地设置大,可能到达Gbit
的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。避免设置过小
:当新生代设置过小时会导致:响应时间优先的应用
:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片,高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在新生代和老年代回收上的时间比例。吞吐量优先的应用
:一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽存放长期存活对象。
3.2 GC优化
3.2.1 GC性能衡量指标
3.2.1.1 内存占用
垃圾回收过程,收集器也需要占用一定空间的内存,它是指为保证垃圾收集能够顺利高效地进行而存储的额外信息。比如CMS
和G1
中的RSet
(详见2.4节),特别是G1
,根据经验,G1
至少要耗费大约相当于Java堆容量10%至20%
的额外内存来维持收集器工作。
随着计算机硬件的发展、性能的提升,我们越来越能容忍收集器多占用一点点内存;
硬件性能增长,对软件系统的处理能力是有直接助益的,硬件的规格和性能越高,也有助于降低收集器运行时对应用程序的影响,换句话说,吞吐量会更高。
3.2.1.2 吞吐量
这里的衡量吞吐量是指应用程序所花费的时间
和系统总运行时间
的比值
。我们可以按照这个公式来计算 GC 的吞吐量:
系统总运行时间
=应用程序耗时
+GC 耗时
。
如果系统运行了100 分钟
,GC 耗时1 分钟
,则系统吞吐量为99%
。
GC 的吞吐量一般不能低于 95%
。
3.2.1.3 延迟
现阶段GC越来越看重延迟时间,假如一个垃圾收集器Stop the World
太长,显然对于应用或者用户来说是比较难受的。就如同打游戏,内存就那么大,假如要清理一些垃圾而要暂停游戏一下,恐怕砸手机的心都有。
特别的,如同3.2.1.1节
提到的,硬件性能增长,虽然吞吐量上去了,内存占用影响降低了,但对于延迟来说,反而是负面影响增大了。虚拟机要回收完整的1TB
的堆内 存,毫无疑问要比回收1GB
的堆内存耗费更多时间。
对于串行收集器
而言,停顿时间可能会比较长;而使用并发收集器
,由于垃圾收集器和应用程序交替运行,程序的停顿时间就会变短,但其效率很可能不如独占垃圾收集器,系统的吞吐量也很可能会降低。
下图中,浅色阶段
表示必须挂起用户线程,深色
则表示收集器线程与用户线程是并发
工作的。
由图可见:
- 在
CMS
和G1
之前的全部收集器,其工作的所有步骤都会产生Stop The World
式的停顿; CMS
和G1
分别使用增量更新
和原始快照
(见2.2节)技术,实现了标记阶段的并发
,不会因管理的堆内存变大,要标记的对象变多而导致停顿时间随之增长。但是对于标记阶段之后
的处理,仍未得到妥善解决。CMS
使用标记-清除
算法,虽然避免了整理阶段收集器带来的停顿,但是清除算法
不论如何优化改进,在设计原理上避免不了空间碎片
的产生,随着空间碎片不断淤积最终依然逃不过Stop The World
的命运。G1
虽然可以按更小的粒度进行回收,从而抑制整理阶段
出现时间过长地停顿,但毕竟也还是要暂停的。
- 最后的两款收集器,
Shenandoah
和ZGC
,几乎整个工作过程全部都是并发的
,只有初始标记
、最终标记
这些阶段有短暂地停顿,这部分停顿的时间基本上是固定的,与堆的容量
、堆中对象的数量
没有正比例关系。实际上,它们都可以在任意可管理的堆容量下,实现垃圾收集的停顿都不超过十毫秒
这种以前听起来是天方夜谭、匪夷所思的目标。
3.2.1.4 垃圾回收频率
通常垃圾回收的频率越低越好,增大堆内存空间
可以有效降低垃圾回收发生的频率,但同时也意味着堆积的回收对象越多,最终也会增加回收时的停顿时间
。所以我们需要适当地增大堆内存空间,保证正常的垃圾回收频率即可。
3.2.2 GC调优策略
3.2.2.1 降低 Minor GC 频率
由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC
,因此我们可以通过增大新生代空间
来降低 Minor GC 的频率。
单次 Minor GC 时间
是由两部分组成:T1
(扫描新生代)和T2
(复制存活对象)。
- 情况 1:假设一个对象在 Eden 区的存活时间为
500ms
,Minor GC 的时间间隔是300ms
,因为这个对象存活时间>间隔时间
,那么正常情况下,Minor GC 的时间为:T1+T2
。 - 情况 2:当我们
增大新生代空间
,Minor GC 的时间间隔可能会扩大到600ms
,此时一个存活500ms
的对象就会在 Eden 区中被回收掉,此时就不存在复制存活对象了,所以再发生 Minor GC 的时间为:T1*2(空间大了)+T2*0
可见,扩容后,Minor GC 时增加了T1
,但省去了T2
的时间。
在 JVM 中,复制对象的成本
要远高于扫描成本
。如果在堆内存中存在较多的长期存活的对象,此时增加年轻代空间,反而会增加 Minor GC 的时间。如果堆中的短期对象很多,那么扩容新生代,单次 Minor GC 时间不会显著增加。因此,单次 Minor GC 时间
更多取决于 GC 后存活对象的数量
,而非 Eden 区的大小。
3.2.2.2 降低 Full GC 频率
由于堆内存空间不足
或老年代对象太多
,会触发 Full GC
,频繁的 Full GC 会带来上下文切换,增加系统的性能开销。 常用措施有:
减少创建大对象
:在平常的业务场景中,我们一次性从数据库中查询出一个大对象用于 web 端显示。比如,一次性查询出 60 个字段的业务操作,这种大对象如果超过年轻代最大对象阈值,会被直接创建在老年代;即使被创建在了年轻代,由于年轻代的内存空间有限,通过 Minor GC 之后也会进入到老年代。这种大对象很容易产生较多的 Full GC。增大堆内存空间
:在堆内存不足的情况下,增大堆内存空间,且设置初始化堆内存为最大堆内存,也可以降低 Full GC 的频率。
3.2.2.3 选择合适的GC收集器
如果要求每次操作的响应时间必须在 500ms
以内。这个时候我们一般会选择响应速度较快的 GC 收集器,
- 堆内存比较小的情况下(<6G)选择
CMS
收集器 - 堆内存比较大的情况下(>8G)
G1
收集器.
3.3 预估调优
3.3.1 调优分类
3.3.1.1 JVM 预调优
包括业务场景设定
、无监控不优化
(要压测),具体步骤可拆分为:
- 计算内存需求:并不是分配的内存越大越好,越大虽然可能对吞吐量或者内存吃紧带来正面影响,但响应时间(延迟)却不一定了。
虚拟机栈的大小在高并发情况下可以变小。
元空间(方法区)保险起见还是设定一个最大的值(默认情况下元空间是没有大小限制的),一般限定几百 M
就够用了. - 选定 CPU
- 选择合适的垃圾收集器
- 设定新生代大小、分代年龄
- 设定日志参数
3.3.1.2 优化 JVM 运行环境(慢、卡顿等)
一般造成 JVM 卡或者慢的原因无非两个部分,一个是 CPU 占用过高,一个是内存占用过高。所以这个时候需要我们进行问题的排查,进行具体的故障分析。3.3.1.3 解决 JVM 中的问题(OOM 等)
比如栈溢出
、堆溢出
、方法区溢出
、本机直接内存溢出
等。
前面两节的
内存优化
与GC 优化
主要就是做的第一种(3.3.1.1节)和第二种(3.3.1.2节)工作。
4 实战
有空再搞。
5 总结
- 具体问题具体分析,实事求是:GC 调优是个很复杂、很细致的过程,要根据实际情况调整,不同的机器、不同的应用、不同的性能要求调优的手段都是不同的,这些都需要大家平时去积累,去观察,去实践。
- 明确调优思路:
测试
-分析
-调优
三步走。 - 任何调优都需要结合场景,明确已知问题和性能目标,
不能为了调优而调优
,以免引入新的 Bug,带来风险和弊端。