生成 jfr
JDK飞行记录器(JFR)是一种结构化日志记录工具, 它记录广泛的系统级(system-level)事件。类似于飞机上的黑盒子,它会持续记录飞行数据,用于调查飞行事故。JFR会持续记录JVM中的 一系列事件,用于诊断问题。这种方式的优势是,它会按时间顺序,捕获导致事故的,详细系统信息。JFR被设计的,对于性能影响很小,所以 可以安全地在生产环境长时间运行。
优势:
- 对于运行系统的影响比较小,额外占用资源小于 1%
- 生成的文件比较小,通常生成 10 多分钟的文件往往小于 1G
查询 java 进程
通过 jps 命令查询。
通过 ps -ef | grep java 查询。
生成 jfr 文件
生成飞行记录,使用 JFR.start 该实用程序的诊断命令 jcmd。
jcmd 84743 JFR.start duratinotallow=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。
分配热点
分配热点视图与分配调用树一起,允许你直接关注负责创建所选类的方法。就像记录的对象视图,分配热点视图也支持标记当前状态和观察一段时间内的差值。视图中会添加一个差值列,它显示了热点自当_标记当前值_操作被调用后的变化。因为默认情况下,分配视图不会定期更新,所以你必须单击_计算_工具栏按钮以获得一个新数据集然后与基线值比较。
计算热点:
热点分析:
热点的分配类:
找到主要是因为 byte[] 数组分配,成为一个分配热点方法。
测试代码
/**
* VM ARG : -Xms64m -Xmx64m
*/
public static void main(String[] args) throws InterruptedException {
List<byte[]> 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。