摘要
本文从 JVM 内存模型与 GC 机制切入,梳理中间件、数据库、压测报表等关键监控指标,演示 jvisualvm、jconsole 及 jstat/jmap/jstack 等工具的使用方法,给出可落地的 JVM 调优参数与排查思路,帮助读者建立高并发系统性能监控与问题定位的完整方法论。
1. JVM 内存模型

- 程序计数器 Program Counter Register:
- 记录的是正在执行的虚拟机字节码指令的地址,
- 此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError的区域
- 虚拟机:VM Stack
- 描述的是 JAVA 方法执行的内存模型,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法接口等信息
- 局部变量表存储了编译期可知的各种基本数据类型、对象引用
- 线程请求的栈深度不够会报 StackOverflowError 异常
- 栈动态扩展的容量不够会报 OutOfMemoryError 异常
- 虚拟机栈是线程隔离的,即每个线程都有自己独立的虚拟机栈
- 本地方法:Native Stack
- 本地方法栈类似于虚拟机栈,只不过本地方法栈使用的是本地方法
- 堆:Heap
- 几乎所有的对象实例都在堆上分配内存

2. 堆
所有的对象实例以及数组都要在堆上分配。堆是垃圾收集器管理的主要区域,也被称为“GC堆”;也是我们优化最多考虑的地方。
堆可以细分为:
- 新生代
- Eden 空间
- From Survivor 空间
- To Survivor 空间
- 老年代
- 永久代/元空间
- Java8 以前永久代,受 jvm 管理,java8 以后元空间,直接使用物理内存。因此,默认情况下,元空间的大小仅受本地内存限制。
垃圾回收
从 Java8 开始,HotSpot 已经完全将永久代(Permanent Generation)移除,取而代之的是一个新的区域—元空间(MetaSpace)

3. jconsole 与 jvisualvm
Jdk 的两个小工具 jconsole、jvisualvm(升级版的 jconsole);通过命令行启动,可监控本地和远程应用。远程应用需要配置。
3.1 jvisualvm 能干什么
监控内存泄露,跟踪垃圾回收,执行时内存、cpu 分析,线程分析...

运行:正在运行的
休眠:sleep
等待:wait
驻留:线程池里面的空闲线程
监视:阻塞的线程,正在等待锁
3.2 安装插件方便查看 gc
- Cmd 启动 jvisualvm
- 工具->插件

- 如果 503 错误解决:
-
cmd 查看自己的 jdk 版本,找到对应的

-
复制下面查询出来的链接。并重新设置上即可

4. 监控指标
4.1 中间件指标
常用的中间件例如Tomcat, Weblogic等指标主要包括JVM,ThreadPool,JDBC具体如下:
| 一级指标 | 二级指标 | 单位 | 解释 |
|---|---|---|---|
| GC | GC频率 | 每秒多少次 | java虚拟机垃圾部分回收频率 |
| Full GC频率 | 每小时多少次 | java虚拟机垃圾部分回收频率 | |
| Full GC平均时长 | 秒 | 用于垃圾完全回收的平均时长 | |
| Full GC最大时长 | 秒 | 用于垃圾回收的最大时长 | |
| 堆使用频率 | 个 | 堆使用率 | |
| ThreadPool | Active Thread Count | 个 | 活动的线程数 |
| Pending User Request | 个 | 处于排队的用户请求个数 | |
| JDBC | JDBC Active Collection | 个 | JDBC活动连接数 |
- 当前正在运行的线程数不能超过设定的最大值。一般情况下系统性能较好的情况下,线程数最小值设置 50 和最大值设置 200 比较合适。
- 当前运行的 JDBC 连接数不能超过设定的最大值。一般情况下系统性能较好的情况下,JDBC 最小值设置 50 和最大值设置 200 比较合适。
- GC频率不能频繁,特别是 FULL GC 更不能频繁,一般情况下系统性能较好的情况下,JVM 最小堆大小和最大堆大小分别设置 1024M 比较合适。
4.2 数据库指标
常用的数据库例如MySql指标主要包括SQL、吞吐量、缓存命中率、连接数等,具体如下:
| 一级指标 | 二级指标 | 单位 | 解释 |
|---|---|---|---|
| SQL | 耗时 | 微秒 | 执行SQL耗时 |
| 吞吐量 | QPS | 个 | 每秒查询次数 |
| TPS | 个 | 每秒事务次数 | |
| 命中率 | Key Buffer命中率 | 百分之 | 索引缓冲区命中率 |
| InnoDB Buffer命中率 | 百分之 | InnoDB缓冲区命中率 | |
| Query Cache命中率 | 百分之 | 查询缓存命中率 | |
| Table Cache命中率 | 百分之 | 表缓存命中率 | |
| Thread Cache命中率 | 百分之 | 线程缓存命中率 | |
| 锁 | 等待次数 | 次 | 锁等待次数 |
| 等待时间 | 微秒 | 锁等待时间 |
- SQL 耗时越小越好,一般情况下微秒级别。
- 命中率越高越好,一般情况下不能低于 95%。
- 锁等待次数越低越好,等待时间越短越好。
| 压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
|---|---|---|---|---|
| Nginx | 50 | 2335 | 11 | 944 |
| Gateway | 50 | 10367 | 8 | 31 |
| 简单服务 | 50 | 11341 | 8 | 17 |
| 首页一级菜单渲染 | 50 | 270(db,thymeleaf) | 267 | 365 |
| 首页渲染(开缓存) | 50 | 290 | 251 | 365 |
| 首页渲染(开缓存、 优化数据库、关日 志) | 50 | 700 | 105 | 183 |
| 三级分类数据获取 | 50 | 2(db)/8(加索引) | ... | ... |
| 三级分类(优化业 务) | 50 | 111 | 571 | 896 |
| 三 级 分 类 ( 使 用 redis 作为缓存) | 50 | 411 | 153 | 217 |
| 首页全量数据获取 | 50 | 7(静态资源) | ||
| Nginx+Gateway | 50 | |||
| Gateway+简单服务 | 50 | 3126 | 30 | 125 |
| 全链路 | 50 | 800 | 88 | 310 |
- 中间件越多,性能损失越大,大多都损失在网络交互了;
- 业务:
- Db(MySQL 优化)
- 模板的渲染速度(缓存)
- 静态资源
5. JVM 分析&调优
JVM调优,调的是稳定,并不能带给你性能的大幅提升。服务稳定的重要性就不用多说了,保证服务的稳定,gc 永远会是 Java 程序员需要考虑的不稳定因素之一。复杂和高并发下的服务,必须保证每次 gc 不会出现性能下降,各种性能指标不会出现波动,gc 回收规律而且干净,找到合适的 jvm 设置。Full gc 最会影响性能,根据代码问题,避免full gc 频率。可以适当调大年轻代容量,让大对象可以在年轻代触发 yong gc,调整大对象在年轻代的回收频次,尽可能保证大对象在年轻代回收,减小老年代缩短回收时间;
5.1 几个常用工具
| 命令 | 功能描述 |
|---|---|
| jstack | 查看 jvm 线程运行状态,是否有死锁现象等等信息 |
| jinfo | 可以输出并修改运行时的 java 进程的 opts。 |
| jps | 与 unix 上的 ps 类似,用来显示本地的 java 进程,可以查看本地运行着几个java程序,并显示他们的进程号。 |
| jstat | 一个极强的监视 VM 内存工具。可以用来监视 VM 内存内的各种堆和非堆的大小及其内存使用量。 |
| jmap | 打印出某个 java 进程(使用 pid)内存内的所有'对象'的情况(如:产生那些对象,及其数量) |
5.2 命令示例
| jstat 工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程 id,和所选参数。 | |
|---|---|
| jstat -class pid | 显示加载 class 的数量,及所占空间等信息 |
| jstat -compiler pid | 显示 VM 实时编译的数量等信息。 |
| jstat -gc pid | 可以显示 gc 的信息,查看 gc 的次数,及时间 |
| jstat -gccapacity pid | 堆内存统计,三代(young,old,perm)内存使用和占用大小 |
| jstat -gcnew pid | 新生代垃圾回收统计 |
| jstat -gcnewcapacity pid | 新生代内存统计 |
| jstat -gcold pid | 老年代垃圾回收统计 |
| 除了以上一个参数外,还可以同时加上 两个数字,如:jstat -printcompilation 3024 250 6 是每 250 毫秒打印一次,一共打印 6 次,还可以加上-h3 每三行显示一下标题。 | |
| jstat -gcutil pid 1000 100 : 1000ms 统计一次 gc 情况统计 100 次; | |
| jinfo 是 JDK 自带的命令,可以用来查看正在运行的 java 应用程序的扩展参数,包括JavaSystem 属性和 JVM 命令行参数;也可以动态的修改正在运行的 JVM 一些参数。当系统崩溃时,jinfo 可以从 core 文件里面知道崩溃的 Java 应用程序的配置信息 | |
|---|---|
| jinfo pid | 输出当前 jvm 进程的全部参数和系统属性 |
| jinfo -flag name pid | 可以查看指定的 jvm 参数的值;打印结果:-无此参数,+有 |
| jinfo -flag [+|-]name pid | 开启或者关闭对应名称的参数(无需重启虚拟机) |
| jinfo -flag name=value pid | 修改指定参数的值 |
| jinfo -flags pid | 输出全部的参数 |
| jinfo -sysprops pid | 输出当前 jvm 进行的全部的系统属性 |
| jmap 可以生成 heap dump 文件,也可以查看堆内对象分析内存信息等,如果不使用这个命令,还可以使用-XX:+HeapDumpOnOutOfMemoryError 参数来让虚拟机出现OOM的时候自动生成 dump 文件。 | |
|---|---|
| jmap -dump:live,format=b,file=dump.hprof pid | dump 堆到文件,format 指定输出格式,live 指明是活着的对象,file 指定文件名。eclipse 可以打开这个文件 |
| jmap -heap pid | 打印 heap 的概要信息,GC 使用的算法,heap 的配置和使用情况,可以用此来判断内存目前的使用情况以及垃圾回收情况 |
| jmap -finalizerinfo pid | 打印等待回收的对象信息 |
| jmap -histo:live pid | 打印堆的对象统计,包括对象数、内存大小等。jmap -histo:live这个命令执行,JVM 会先触发 gc,然后再统计信息 |
| jmap -clstats pid | 打印 Java 类加载器的智能统计信息,对于每个类加载器而言,对于每个类加载器而言,它的名称,活跃度,地址,父类加载器,它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。 |
| jmap -F -histo pid | -F 强制模式。如果指定的 pid 没有响应,请使用 jmap -dump 或jmap -histo 选项。此模式下,不支持 live 子选项。 |
| jstack 是 jdk 自带的线程堆栈分析工具,使用该命令可以查看或导出 Java 应用程序中线程堆栈信息。 | |
|---|---|
| jstack pid | 输出当前 jvm 进程的全部参数和系统属性 |