JMeter 压测教程之二 JVM 调优

大纲

JVM 简单介绍

JVM 内存结构

JVM 内存结构主要有三大块:栈、堆内存、方法区。堆内存是 JVM 中最大的一块。方法区存储类信息、静态变量、常量、常量池等数据,是线程共享的区域,为了与 Java 堆区分,方法区还有一个别名 Non-Heap (非堆)。栈又分为 Java 虚拟机栈和本地方法栈,主要用于方法的执行。

JVM 堆内存

所有的对象实例以及数组都要在堆内存上分配,堆内存是垃圾收集器管理的主要区域,也被称为 GC 堆。堆内存由新生代和老年代组成,不包括永久代(方法区);而新生代内存又被分成 Eden 空间、From Survivor 空间、To Survivor 空间,默认情况下新生代按照 8:1:1 的比例来分配。

提示

从 Java 8 开始,HotSpot 已经完全将永久代(Permanent Generation)移除,取而代之的是一个新的区域 — 元空间(MetaSpace)。

JVM 性能监控

为了方便监控 JVM 的性能,JDK 提供了 jconsolejvisualvm 工具,两者都可以通过命令行直接启动,支持监控本地和远程应用。值得一提的是,推荐使用 jvisualvm,因为它可以看作是升级版的 jconsole

Jconsole 监控

启动监控

  • 启动命令
1
$ jconsole
  • 运行界面

Jvisualvm 监控

jvisualvm 可以监控内存泄露、跟踪垃圾回收、执行时内存分析、CPU 分析、线程分析等。

启动监控

  • 启动命令
1
$ jvisualvm
  • 运行界面

安装插件

为了方便查看 GC 的情况,jvisualvm 需要提前安装指定的插件。

  • 第一步:查看 JDK 版本
1
$ java -version
  • 第二步:浏览器打开 官方插件中心 的页面,根据 JDK 版本找到 Java VisualVM 的更新链接,例如 https://visualvm.github.io/archive/uc/8u40/updates.xml.gz

  • 第三步:菜单栏导航到 工具 -> 插件 -> 设置,点击 编辑 按钮,将 URL 更改为上面找到的 Java VisualVM 更新链接

  • 第四步:菜单栏导航到 工具 -> 插件 -> 可用插件,点击 检查最新版本 按钮,等插件列表更新成功后,勾选 Visual GC 项,最后点击 安装 按钮即可。

  • 第六步:重启 jvisualvm 后,选择要监控的应用,若在标签页中看到 Visual GC 页面,则说明 GC 插件安装成功。

性能监控指标

中间件指标

常用的中间件(如 Tomcat、Weblogic)监控指标,主要包括 JVM、ThreadPool、JDBC 等,具体如下:

  • 当前正在运行的线程数不能超过设定的最大值。一般情况下系统性能较好的情况下,线程数最小值设置为 50 和最大值设置为 200 比较合适。
  • 当前运行的 JDBC 连接数不能超过设定的最大值。一般情况下系统性能较好的情况下,JDBC 最小值设置为 50 和最大值设置为 200 比较合适。
  • GC 频率不能频繁,特别是 FULL GC 更不能频繁,一般情况下系统性能较好的情况下,JVM 最小堆大小和最大堆大小分别设置 1024M 比较合适。

数据库指标

常用的数据库(如 MySQL)监控指标,主要包括 SQL 性能、吞吐量、缓存命中率、锁、连接数等,具体如下:

  • SQL 执行耗时越小越好,一般情况下微秒级别。
  • 缓存命中率越高越好,一般情况下不能低于 95%。
  • 锁等待次数越低越好,等待时间越短越好。

中间件压测案例

以简单的电商商城项目为例,各中间件的压测结果如下:

总结

  • 中间件越多,性能损失越大,大多都损失在网络交互上。
  • 业务优化方向:数据库、模板页面的渲染速度、静态资源。

JVM 分析 & 调优

JVM 调优,调的是稳定,并不能让性能得到大幅提升。服务稳定的重要性就不用多说了,保证服务的稳定,GC 永远会是 JAVA 程序员需要考虑的不稳定因素之一。复杂和高并发下的服务,必须保证每次 GC 不会出现性能下降,各种性能指标不会出现波动,GC 回收规律而且干净,找到合适的 JVM 设置。FULL GC 最会影响性能,根据代码问题,避免 FULL GC 频率。可以适当调大年轻代的容量,让大对象可以在年轻代触发 YONG GC,调整大对象在年轻代的回收频次,尽可能保证大对象在年轻代回收,减小老年代缩短回收时间。

常用工具

工具说明
jstack 查看 JVM 线程运行状态,是否有死锁现象等信息
jinfo 可以输出并修改运行时的 Java 进程的 opts
jps 与 Unix 上的 ps 命令类似,用来显示本地的 Java 进程,可以查看本地运行着几个 Java 程序,并显示它们的进程号
jstat 一个极强的监视 VM 内存工具。可以用来监视 VM 内存内的各种堆和非堆的大小及其内存使用量
jmap 打印出某个 Java 进程(使用 pid)内存内的所有 对象 的情况(如:产生哪些对象及其数量)

工具使用

在使用下述工具前,建议先用 jps 命令获取当前的每个 JVM 进程号,然后选择要查看的 JVM。

jstat 使用

jstat 工具特别强大,参数有众多的可选项,可详细地查看堆内各个部分的使用量,以及类加载的数量。使用时,需加上应用的进程 id 和所选参数。

命令说明
jstat -class pid 显示加载 Class 的数量,及所占空间等信息
jstat -compiler pid 显示 VM 实时编译的数量等信息
jstat -gc pid 显示 GC 的信息,查看 GC 的次数与时间
jstat -gccapacity pid 堆内存统计,包括堆内存的使用和占用大小
jstat -gcnew pid 新生代垃圾回收统计
jstat -gcnewcapacity pid 新生代内存统计
jstat -gcold pid 老年代垃圾回收统计
jstat -gcutil pid 堆内存(包括新生代、老年代)的垃圾回收统计

除了以上 pid 参数外,还可以同时加上两个数字,示例如下:

  • jstat -gcutil pid 1000 100: 每 1000 毫秒统计一次 GC 情况,一共统计 100 次
  • jstat -printcompilation pid 250 6: 表示每 250 毫秒打印一次,一共打印 6 次,还可以加上 -h3 参数使每三行显示一次标题

jinfo 使用

jinfo 是 JDK 自带的命令,可以用来查看正在运行的 Java 应用程序的扩展参数,包括 Java System 属性和 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 使用

jmap 命令可以生成堆内存的 Dump 文件,也可以查看堆内对象分析内存信息等,如果不使用这个命令,还可以使用 -XX:+HeapDumpOnOutOfMemoryError 参数来让虚拟机在出现 OOM 的时候自动生成 Dump 文件。

命令说明
jmap -dump:live,format=b,file=product.dump pidDump 堆内存到指定的文件,format 指定输出格式,live 指明是活着的对象,file 指定文件名。Eclipse 可以直接打开这个文件
jmap -heap pid 打印堆内存的概要信息,包括 GC 使用的算法、堆内存的配置和使用情况,可以用此来判断目前内存的使用情况以及垃圾回收情况
jmap -finalizerinfo pid 打印等待回收的对象信息
jmap -histo:live pid 打印堆的对象统计,包括对象数、内存大小等。特别注意,这个命令执行,JVM 会先触发一次 GC,然后再统计信息
jmap -clstats pid 打印 Java 类加载器的智能统计信息,对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印

-F 参数表示强制模式。如果指定的 pid 没有响应,请使用 jmap -dumpjmap -histo 选项。此模式下,不支持 live 子选项。使用示例:jmap -F -histo pid

jstack 使用

jstack 是 JDK 自带的线程堆栈分析工具,使用该命令可以查看或导出 Java 应用程序中的线程堆栈信息。

命令说明
jstack pid 输出当前 JVM 进程的线程堆栈信息