JVM 性能调优之通过 JProfile 和 JFR 分析系统瓶颈提升系统性能

生成 jfr

JDK飞行记录器(JFR)是一种结构化日志记录工具, 它记录广泛的系统级(system-level)事件。类似于飞机上的黑盒子,它会持续记录飞行数据,用于调查飞行事故。JFR会持续记录JVM中的 一系列事件,用于诊断问题。这种方式的优势是,它会按时间顺序,捕获导致事故的,详细系统信息。JFR被设计的,对于性能影响很小,所以 可以安全地在生产环境长时间运行。
优势:
对于运行系统的影响比较小,额外占用资源小于 1%
生成的文件比较小,通常生成 10 多分钟的文件往往小于 1G
查询 java 进程

通过 jps 命令查询

图片

image.png
通过 ps -ef | grep java 查询
生成 jfr 文件

生成飞行记录,使用 JFR.start 该实用程序的诊断命令 jcmd。
jcmd 84743 JFR.start duration=5m settings=profile filename=~/jfr/xxkk.jfr

JProfile 介绍

JProfiler是一个用于分析运行JVM内部情况的专业工具。在开发中你可以使用它,用于质量保证,也可以解决你的生产系统遇到的问题。
JProfiler处理四个主要问题:
方法调用这通常被称为”CPU分析”。方法调用可以通过不同的方式进行测量和可视化, 分析方法调用可以帮助了解你的应用程序正在做什么,并找到提高其性能的方法。
分配分析堆上对象的分配、引用链和垃圾回收属于”内存分析”的范畴。这个功能可以让你解决内存泄漏,总之使用更少的内存,分配更少的临时对象。
线程和锁线程可以持有锁,例如通过在一个对象上做同步。当多个线程协作时,可能会出现死锁,JProfiler可以为你可视化这种情况。此外,锁可能被争用,这意味着线程在获得锁之前必须等待。通过JProfiler可以深入了解线程及其各种锁情形。
高层子系统许多性能问题发生在更高的语义层面。例如,对于JDBC调用,你可能想找出哪条SQL语句是最慢的。对于这样的子系统,JProfiler提供了”探针”,将特定有效载荷附加到调用树。
JProfiler的UI是一个桌面应用程序。你可以以交互的方式实时分析JVM,也可以在不使用UI的情况下,自动化分析。保存在快照中的分析数据,可以通过JProfiler UI打开。此外,命令行工具和构建工具集成可以帮助你自动分析会话。
注意:JProfile 是商业软件,希望大家在使用的过程中购买正版授权
内存分析

记录的对象
内存分析中,可以通过记录的对象找到最耗费内存的对象。只有总对象大小超过固定阈值(通常是堆的1%)的类才会被记录。默认情况下,JFR中禁用了这个功能,因为它会引入大量的开销。
下面两个字段的解释:
**实例计数示例, **可以看到某个用例的堆上还剩下哪些对象(实际上会小于总大小除以每个对象的平均大小)。
预估总大小,这个是一个预估值,预估在开始 jfr 记录,到 jfr 记录结束这个类的实例总大小

https://docs.oracle.com/en/java/javase/17/docs/specs/man/jfr.html分配热点

分配热点视图与分配调用树一起,允许你直接关注负责创建所选类的方法。就像记录的对象视图,分配热点视图也支持标记当前状态和观察一段时间内的差值。视图中会添加一个差值列,它显示了热点自当标记当前值操作被调用后的变化。因为默认情况下,分配视图不会定期更新,所以你必须单击计算工具栏按钮以获得一个新数据集然后与基线值比较。
计算热点

image.png
热点分析

image.png
热点的分配类

image.png
找到主要是因为 byte[] 数组分配,成为一个分配热点方法。
测试代码

/**

  • VM ARG : -Xms64m -Xmx64m
    */
    public static void main(String[] args) throws InterruptedException {
    List list = new ArrayList<>(1000);
    //2kb * 10 * 120 = 2400kb
    //数量 = 10 * 120 / 10 = 120
    for (int i = 0; i < 10000; i++) {
    Thread.sleep(100);
    byte[] arr = new byte[1024 * 2];
    list.add(arr);
    }
    }

CPU 分析

调用树

跟踪所有的方法调用及其调用栈会消耗相当大的内存,短时间内就会耗尽所有内存。另外,在一个繁忙的JVM中,很难直观获得方法调用的数量。通常情况下,这个数字是如此之大,以至于定位和跟随跟踪是不可能的。
另一个方面,只有将收集到的数据进行汇总,许多性能问题才会变得清晰。这样,你就可以知道在某个时间段内,方法调用相对于整个活动的重要性。如果是单一的跟踪,你对你所看的数据的相对重要性没有概念。
这就是为什么JProfiler建立了一个所有观察到的调用堆栈的累积树,并注解有观察到的时间和调用次数。时间顺序信息被消除,只保留总数。树中的每个节点代表一个至少被观察过一次的调用堆栈。节点的子节点代表在该调用堆栈中看到的所有传出调用。

调用树是”CPU视图”部分的第一个视图,当你开始进行CPU分析时,它是一个很好的起点, 因为遵循方法调用从起点到最细化的终点的自上而下视图,最容易理解。JProfiler按照子节点的总时间进行排序,所以你可以深度优先打开树,分析对性能影响最大的部分。

热点

如果你的应用程序运行得太慢,你要找到那些占用大部分时间的方法。通过调用树,有时可以直接找到这些方法, 但通常这样做是行不通的,因为调用树可能很大而且有大量叶节点
在这种情况下,你需要反转调用树:一个所有方法的列表,按其总的自身时间排序,从所有不同的调用堆栈中累计出来, 并通过回溯跟踪显示这些方法是如何被调用的。在热点树中,叶节点是入口点, 就像应用程序的main 方法或线程的run 方法。从热点树中最深的节点开始,调用向上传递到顶层节点。

回溯跟踪中的调用次数和执行时间并不是指该方法节点,而是指顶层热点节点在这条路径上被调用的次数。理解这一点很重要:粗略一看,你会认为看到的节点上的信息是该节点的调用次数。然而,在热点树中,该信息显示的是该节点对顶层节点的贡献。所以,你必须这样理解这些数字: 沿着这个倒置的调用堆栈,顶层热点被调用了n 次,总持续时间为t 秒。

通过这里可以看到这个 CASE 是因为正则导致的 CPU 热点
测试代码

static String pattern = ” ^([u4e00-u9fa5]+)((·[u4e00-u9fa5]+)+|([u4e00-u9fa5]+))$”;
static String defaultName = “张三·无论其是看都看呐阿斯顿啊·萨肯萨肯打开你发都看啊看你发个卡看那可能发看那个可能看呐”;

public static void main(String[] args) throws InterruptedException {
int time = 1;
String result;
while (true) {
if (time > 0) {
Thread.sleep(time);
}
result = defaultName.matches(pattern) ? “姓名合法” : “姓名不合法”;
}
}

参考文档

jcmd 指令详解:https://docs.oracle.com/javacomponents/jmc-5-5/jfr-command-reference/diagnostic-command-reference.htm
生成 jfr 文件:https://docs.oracle.com/javacomponents/jmc-5-5/jfr-runtime-guide/run.htm
jprofile 中文手册:https://www.ej-technologies.com/resources/jprofiler/v/13.0/help_zh_CN/doc/main/memory.html

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/414472.html

(0)
联系我们
联系我们
分享本页
返回顶部