一、基本概述
1.1、介绍
在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在eclipse里面有 Eclipse Memory Analyzer tool(MAT)插件可以测试,而在IDEA中也有这么一个插件,就是JProfiler。
3Profiler是由 ej-technologies 公司开发的一款Java应用性能诊断工具。功能强大,但是收费。
官网下载地址: https://www.ej-technologies.com/products/jprofiler/overview.html
1.2、特点
- 使用方便、界面操作友好(简单且强大)
- 对被分析的应用影响小(提供模板)
- CPU, Thread ,Memory分析功能尤其强大
- 支持对jdbc ,noSql, jsp, servlet,socket等进行分析
- 支持多种模式(离线,在线)的分析
- 支持监控本地、远程的JVM
- 跨平台,拥有多种操作系统的安装版本
1.3、主要功能
1.3.1-方法调用
对方法调用的分析可以帮助您了解应用程序正在做什么,并找到提高其性能的方法
1.3.2-内存分配
通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄漏问题,优化内存使用
1.3.3-线程和锁
JProfiler提供多种针对线程和锁的分析视图助您发现多线程问题
1.3.4-高级子系统
许多性能问题都发生在更高的语义级别上。例如,对于JDBC调用,您可能希望找出
执行最慢的SQL语句。JProfiler支持对这些子系统进行集成分析
二、安装与配置
2.1、下载与安装
https://www.ej-technologies.com/download/jprofiler/version_100
2.2、JProfiler中配置IDEA
2.3、IDEA集成JProfiler
方式一:在idea插件中安装
方式二:从官方下载
https://plugins.jetbrains.com/plugin/253-jprofiler
三、具体试用
3.1、数据采集方式
JProfier数据采集方式分为两种:Sampling(样本采集)和Instrumentation(重构模式)
- Instrumentation:这是Profiler全功能模式。在class加载之前,JProfier把相关功能代码写入到需要分析的class的bytecode中,对正在运行的jvm有一定影响。
- 优点:功能强大。在此设置中,调用堆栈信息是准确的。
- 缺点:若要分析的class较多,则对应用的性能影响较大, CPU开销可能很高(取决于Filter的控制)。因此使用此模式一般配合Filter使用,只对特定的类或包进行分析。
- Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来。
- 优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)
- 缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)
注:JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是JProfiler的数据采集类型。
3.2、遥感监测Telemetries
遥感监测Telemetries(查看VM的运行信息)
- 整体视图overview:显示堆内存、cpu、线程以及GC等活动视图
- 内存Memory:显示一张关于内存变化的活动时间表。
- 记录的对象 Recorded objects:显示一张关于活动对象与数组的图表的活动时间表。
- 记录吞吐量 Record Throughput:显示一段时间累计的JVM生产和释放的活动时间表。
- 垃圾回收活动GC Activity:显示一张关于垃圾回收活动的活动时间表。
- 类classes:显示一个与已装载类的图表的活动时间表。
- 线程Threads:显示一个与动态线程图表的活动时间表
CPU负载 CPU Load:显示一段时间中CPU的负载图表。
3.3、内存视图Live Memory
Live memory内存剖析: class/class instance的相关信息。例如对象的个数,大小,对象创建的方法执行栈,对象创建的热点。
所有对象All 0bjects
显示所有加载的类的列表和在堆上分配的实例数。只有Java 1.5 (JVMTI)才会显示此视图。
- 记录对象 Record 0bjects
查看特定时间段对象的分配,并记录分配的调用堆栈。
- 分配访问树Allocation Call Tree
显示一棵请求树或者方法、类、包或对已选择类有带注释的分配信息的J2EE组件。
- 分配热点Allocation Hot Spots
显示一个列表,包括方法、类、包或分配已选类的32EE组件。你可以标注当前值并且显示差异值。对于每个热点都可以显示它的跟踪记录树。
- 类追踪器Class Tracker
类跟踪视图可以包含任意数量的图表,显示选定的类和包的实例与时间。
分析:内存中的对象的情况
>频繁创建的Java对象:死循环、循环次数过多
>存在大的对象:读取文件时,byte[]应该边读边写。—>如果长时间不写出的话,导致byte[]过大
>存在内存泄漏
3.4、堆遍历heap walker
类classes
显示所有类和它们的实例,可以右击具体的类”Used Selected Instance”实现进一步跟踪。
分配 Allocations
为所有记录对象显示分配树和分配热点。
索引 References
为单个对象和“显示到垃圾回收根目录的路径”提供索引图的显示功能。还能提供合并输入视图和输出视图的功能。
时间 Time
显示一个对已记录对象的解决时间的柱状图。
检查Inspections
显示了一个数量的操作,将分析当前对象集在某种条件下的子集,实质是一个筛选的过程。
图表Graph
你需要在references视图和biggest视图手动添加对象到图表,它可以显示对象的传入和传出引用,能方便的找到垃圾收集器根源。
Ps :在工具栏点击”Go To Start”可以使堆内存重新计数,也就是回到初始状态。
3.5、cpu视图cpu views
Profiler提供不同的方法来记录访问树以优化性能和细节。线程或者线程组以及线程状况可以被所有的视图选择。所有的视图都可以聚集到方法、类、包或J2EE组件等不同层上。
访问树 Call Tree
显示一个积累的自顶向下的树,树中包含所有在JVM中已记录的访问队列。JDBC, JMS和NDI服务请求都被注释在请求树中。请求树可以根据Servlet和3SP对URL的不同需要进行拆分。
热点Hot Spots
显示消耗时间最多的方法的列表。对每个热点都能够显示回溯树。该热点可以按照方法请求,3DBC,MS和JNDI服务请求以及按照URL请求来进行计算。
访问图 Call Graph
显示一个从已选方法、类、包或32EE组件开始的访问队列的图。
方法统计Method Statistis
显示一段时间内记录的方法的调用时间细节。
3.6、线程视图threads团
了Profiler通过对线程历史的监控判断其运行状态,并监控是否有线程阻塞产生,还能将一个线程所管理的方法以树状形式呈现。对线程剖析。
线程历史 Thread History
显示一个与线程活动和线程状态在一起的活动时间表。
线程监控Thread Monitor
显示一个列表,包括所有的活动线程以及它们目前的活动状况。
线程转储 Thread Dumps
显示所有线程的堆栈跟踪。
线程分析主要关心三个方面:
1. web容器的线程最大数。比如:Tomcat的线程容量应该略大于最大并发数。
2.线程阻塞
3.线程死锁
3.7、监视器&锁Monitors&locks
监控和锁Monitors &Locks所有线程持有锁的情况以及锁的信息。
观察VM的内部线程并查看状态:
- 死锁探测图表Current Locking Graph :显示JVM中的当前死锁图表。
- 目前使用的监测器 Current Monitors :显示目前使用的监测器并且包括它们的关联线程。
- 锁定历史图表 Locking History Graph :显示记录在JVM中的锁定历史。
- 历史检测记录 Monitor History :显示重大的等待事件和阻塞事件的历史记录。
- 监控器使用统计 Monitor Usage Statistics :显示分组监测,线程和监测类的统计监测数据。
四、案例分析
案例1
良性的案例
案例2package com.study.jprofiler;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class JProfilerTest {
public static void main(String[] args) {
while (true){
ArrayList list = new ArrayList();
for (int i = 0; i < 500; i++){
Data data = new Data();
list.add(data);
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Data{
private int size = 10;
private byte[] buffer = new byte[1024 * 1024];
private String info = "hello,chang";
}
内存泄漏 ```java package com.study.jprofiler; import java.util.ArrayList; import java.util.concurrent.TimeUnit; public class MemoryLeak { public static void main(String[] args) {
} } class Bean{ int size = 10; String info = “hello,chang”; static ArrayList list = new ArrayList(); }while (true){
ArrayList beanList = new ArrayList();
for (int i = 0; i < 500; i++){
Bean data = new Bean();
data.list.add(new byte[1024 * 10]);
beanList.add(data);
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
```
一直不断的在占据内存,虽然进行了垃圾回收,但内存占用率还是在不断的增加
因为静态是跟着JVM的结束而结束,所以将Bean类下的ArrayList改成非静态的,JVM就能进行正常的垃圾回收的。