由于最近工作的原因,使用了JProfiler(8)来做性能瓶颈分析,发现这个工具使用起来确实挺方便,现在整理一下JProfiler相关知识。
一.JProfiler是什么
JProfiler是由ej-technologies GmbH公司开发的一款性能瓶颈分析工具(该公司还开发部署工具)。
其特点:
二.数据采集
Q1. JProfiler既然是一款性能瓶颈分析工具,这些分析的相关数据来自于哪里?
Q2. JProfiler是怎么将这些数据收集并展现的?
(图2)
A1. 分析的数据主要来自于下面俩部分
1. 一部分来自于jvm的分析接口JVMTI(JVM Tool Interface) , JDK必须>=1.6
JVMTI is an event-based system. The profiling agent library can register handler functions for different events. It can then enable or disable selected events
例如: 对象的生命周期,thread的生命周期等信息
2. 一部分来自于instruments classes(可理解为class的重写)
例如:方法执行时间,次数,方法栈等信息
A2. 数据收集的原理如图2
1. 用户在JProfiler GUI中下达监控的指令(一般就是点击某个按钮)
2. JProfiler GUI JVM 通过socket(默认端口8849),发送指令给被分析的jvm中的JProfile Agent。
3. JProfiler Agent(如果不清楚Agent请看文章第三部分"启动模式") 收到指令后,将该指令转换成相关需要监听的事件或者指令,来注册到JVMTI上或者直接让JVMTI去执行某功能(例如dump jvm内存)
4. JVMTI 根据注册的事件,来收集当前jvm的相关信息。 例如: 线程的生命周期; jvm的生命周期;classes的生命周期;对象实例的生命周期;堆内存的实时信息等等
5. JProfiler Agent将采集好的信息保存到内存中,按照一定规则统计好(如果发送所有数据,会对被分析的应用网络产生比较大的影响)
6. 返回给JProfiler GUI Socket.
7. JProfiler GUI Socket 将收到的信息返回 JProfiler GUI Render
8. JProfiler GUI Render 渲染成最终的展示效果
三. 数据采集方式和启动模式
A1. JProfier采集方式分为两种:Sampling(样本采集)和Instrumentation
注: JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型 。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据, 所以可以直接认为是JProfiler的数据采集类型。
A2: 启动模式:
四. JProfiler核心概念
五. 实践
为了方便实践,直接以JProfiler8自带的一个例子来帮助理解上面的相关概念。
JProfiler 自带的例子如下:模拟了内存泄露和线程阻塞的场景:
A1. 首先来分析下内存泄露的场景:
1. 在Telemetries-> Memory视图中你会看到大致如下图的场景(在看的过程中可以间隔一段时间去执行Run GC这个功能):老生代在gc后内存的大小在慢慢的增加(理想情况下,这个值应该是稳定的)
(图14)
在Allocations tab项中,右键点击其中的某个方法,可查看到具体的源码信息.
(图19)
【注】:到这里问题已经非常清楚了,明白了在图17中为什么哪些实例的数量是一样多,并且为什么内存在fullgc后还是回收不了(一个old 区的对象leakMap,put的信息也会进入old区, leakMap如回收不掉,那么该map中包含的对象也回收不掉)。具体源码参考: /jprofiler install path/demo/bezier
A2. 模拟线程阻塞的场景
为了方便区分线程,我将Demo中的BezierAnim.java的L236的线程命名为test
public void start() {
thread = new Thread(this, "test");
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
勾选了Demo中"Simulate blocking"选项后,如下图(注意看下下图中的状态图标), test线程block状态明显增加了。
(图21)
在Monitors & locks->Monitor History观察了一段时间后,会发现有4种发生锁的情况。
第一种:
AWT-EventQueue-0 线程持有一个Object的锁,并且处于Waiting状态。
图下方的代码提示出Demo.block方法调用了object.wait方法。这个还是比较容易理解的。
(图22)
第二种:
AWT-EventQueue-0占有了bezier.BezierAnim$Demo实例上的锁,而test线程等待该线程释放。
注意下图中下方的源代码, 这种锁的出现原因是Demo的blcok方法在AWT和test线程
都会被执行,并且该方法是synchronized.
(图23)
第三种和第四种:
test线程中会不断向事件Event Dispatching Thread提交任务,导致竞争java.awt.EventQueue对象锁。
提交任务的方式是下面的代码:repaint()和EventQueue.invokeLater
public void run() {
Thread me = Thread.currentThread();
while (thread == me) {
repaint();
if (block) {
block(false);
}
try {
Thread.sleep(10);
} catch (Exception e) {
break;
}
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
onEDTMethod();
}
});
}
thread = null;
}
六. 最佳实践