目录
01-工具概述
使用上一章命令行工具或组合能帮助我们获取目标Java应用性能相关的基础信息,但它们存在下列局限:
1、无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。
2、要求用户登陆到目标Java应用所在的宿主机上,使用起来不是很方便。
3、分析数据终端输出,结果展示不够直观。
为此,JDK提供了一些内存泄露的分析工具,如jconsole,jvisualvm等,用于辅助开发人员定位问题,但是这些工具很多时候并不满足快速定位的需求。所以这里我们介绍的工具相对多一些、丰富一些。
图形化综合诊断工具
- JDK自带的工具
jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等。位置jdk\bin\jconsole.exe
Visual VM:Visual VM 是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。位置:jdk\bin\jvisualvm.exe
JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。
- 第三方工具
MAT:MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄露和减少内存消耗。它可以是独立软件,也可以Eclipse的插件形式出现。
JProfiler:商业软件,需要付费。功能强大。与Visual VM类似。
Arthas:Alibaba开源的Java诊断工具。深受开发者的喜爱。
Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息。
02-jConsole
基本概述:
- 从Java5开始,在JDK中自带的java监控和管理控制台。
- 用于对JVM内存、线程和类的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。
三种连接方式:
- Local:使用JConsole连接一个正在本地系统运行的JVM,并且执行程序和运行JConsole的需要是同一个用户。JConsole使用文件系统的授权通过RMI连接器连接到平台的MBean服务器上。这种从本地连接的监控能力只有Sun的JDK具有。
- Remote:使用下面的URL通过RMI连接器连接到一个JMX代理,service:jmx:rmi:///jndi/rmi://hostName:portNum/jmxrmi。JConsole为建立连接,需要在环境变量中设置mx.remote.credentials来指定用户名和密码,从而进行授权。
- Advance:使用一个特殊的URL连接JMX代理。一般情况使用自己定制的连接器而不是RMI提供的连接器来连接JMX代理,或者是一个使用JDK1.4的实现了JMX和JMX Romote的应用。
03-Visual VM
基本概述:
- Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具。
- 它集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程即进程的配置和环境信息(jps、jinfo),监视应用程序的CPU、GC、堆、方法区即线程的信息(jstat、jstack)等,甚至代替JConsole。
- 在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisaulVM 在JDK/bin目录下),即:它完全免费。
- 此外,Visaul VM也可以作为独立的软甲安装。
连接方式:
本地连接:监控本地线程的CPU、类、线程等。
远程连接:
1、确定远程服务器的ip地址
2、添加JMX(通过JMX技术具体监控远端服务器哪个Java进程)
3、修改bin/catalina.sh文件,连接远程的tomact
4、在../conf中添加jmxremote.access和jmxremote.password文件
5、将服务器地址改为公网ip地址
6、设置阿里云安全策略和防火墙策略
7、启动 tomcat,查看tomcat启动日志和端口监听
8、JMX中输入端口号、用户名、密码登陆
主要功能:
- 生成、读取堆内存快照
- 查看JVM参数和系统属性
- 查看正在运行的虚拟机进程
- 生成、读取线程快照
- 程序资源的实时监控
- 其他功能:JMX代理连接、远程环境监控、CPU分析和 内存分析
04-eclipse MAT
基本概述:
MAT工具是一款功能强大的Java内存分析器。可用于查找内存泄露以及查看内存消耗情况。
获取dump文件:
dump文件内容:MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。一般来说这些信息含有:
- 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。
- 所有的类信息,包括classloader、类名称、父类、静态变量等。
- GCRoot到所有这些对象的引用路径
- 线程信息,包括线程的调用栈以及线程的线程局部变量(TLS)。
两点说明:
说明1:缺点:MAT不是一个万能工具,它并不能处理所有的堆转储文件。但是比较主流的厂家和格式,例如Sun,HP,SAP所采用的HPROF二进制堆转储文件,以及IBM的PHD堆存储文件都能被很好的解析。
说明2:最吸引人的还是能够快速为开发人员生成内存泄露报表,方便问题定位和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉才能发现。
获取dump文件:
方法1:通过前一章介绍的jmap工具生成,可以生成任意一个java进程的dump文件;
方法2:通过配置JVM参数生成。
- 选项"-XX:+HeapDumpOutOfMemoryError"或"-XX:+HeapDumpBeforeFullGC"
- 选项"-XX:HeapDumpPath"所代表的含义就是当程序出现OutOfMemory时,将会在相应的目录下生成一份dump文件。如果不指定选项"XX:HeapDumpPath"则在当前目录下生成dump文件。
对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用jmap+MAT工具是最常见的组合。
方法3:使用VisualVM可以导出dump文件
方法4:使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动Java程序中导出快照。该功能将借助jps列出当前正在运行的Java进程,以供选择并获取快照。
分析堆dump文件:
1.histogram:展示了各个类的实例数目以及这些实例的Shallow heap或Retainedheap的总和
MAT的直方图和jmap的-histo子命令一样,都能够展示各个类的实例数目以及这些实例的Shallow heap总和。但是,MAT的直方图还能计算Retained heap,并支持基于实例数目或Retained heap的排序方式(默认为 Shallow heap)。此外,MAT还可以将直方图中的类按照超类、类加载器或者包名分组。当选中某个类时,MAT界面左上角的Inspector窗口将展示该类Class实例的相关信息,比如类加载器等。
2.thread overview:查看系统中的Java线程;查看局部变量的信息。
3.获得对象相互引用的关系:with outgoing referneces; with incoming references
4.浅堆和深堆:
shallow heap:浅堆是指一个对象消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同,对象的大小可能会向8字节进行对齐。
以String为例子:两个int值共占8字节,对象引用占4字节,对象头占8字节,合计20字节,向8字节对齐,故占24字节(jdk7中)。
这24字节为String对象浅堆大小。它与String的value实际取值无关,无论字符串长度如何,浅堆的大小始终是24字节。
retained heap:
保留集(Retained Set):
对象A的保留集指当对象A被垃圾回收后,可以释放的所有对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接访问或间接访问到的所有对象的集合。通俗的说,就是指仅被对象A所持有的对象的集合。
深堆(Retained Heap):
深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意:浅堆指对象本身占用的内存,不包括内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,及对象被回收后,可以释放的真实空间。
补充:对象实际大小
另外一个概念是对象的实际大小。这里,对象的实际大小定义为一个对象所能触及的所有对象的浅堆大小之和,也就是我们通常说的对象大小。与深堆相比,似乎这个在日常开发中更为直观和被人接受,但实际上,这个概念和垃圾回收无关。
下图展示了一个简单的对象引用关系图,对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大小只是只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小是A、D之和,由于C还可以通过对象B访问到,因此不在对象A的深堆范围之内。
支配树:
支配树(Dominator Tree)
支配树的概念源自图论。
MAT提供了一个称为支配树(Dominator Tree)的对象图。支配树体现了对象实例间的支配关系。在对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B。如果对象A是距离对象B最近的支配对象,则认为对象A是对象B路径的直接支配者。支配树是基于对象间的引用图锁建立的,它有以下基本性质:
- 对象A的子树(所有被对象A支配的对象集合)表示对象A的保留集(retained set),即深堆。
- 如果对象A支配对象B,那么对象A的直接支配者也支配对象B
- 支配树的边与对象引用图的边不直接对应。
如下图所示:左图表示对象引用图,右图表示左图对应的支配树。对象A和B由根对象直接支配,由于在到对象C的路径中,可以经过A,也可以经过B,因此对象C的直接支配者也是根对象。对象F与对象D相互引用,因为到对象F的所有路径必然经过对象D,因此,对象D是对象F的直接支配者。而到对象D的所有路径中,必然经过对象C,即使是从对象F到对象D的引用,从根节点出发,也是经过对象C的,所以,对象D的直接支配者为对象C。
同理,对象E支配对象G。到达对象H的可以通过对象D,也可以通过对象E,因此对象D和E都不能支配对象H,而经过对象C既可以到达D也可以到达E,因此对象C为对象H的支配者。
在MAT中,单击工具栏上的对象支配者树按钮,可以打开对象支配者视图。
补充1: 再谈内存泄露
内存泄露的理解与分类:
何为内存泄露(memory leak)?
可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄露问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄露)。
内存泄露(memory leak)的理解:
严格来说,只有对象不会再被程序用到了,但是GC又不能回收它们的情况,才叫做内存泄露。但实际情况中,可能会出现一些疏忽导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的内存泄露。
对象X引用对象Y,X的生命周期比Y的生命周期长;那么当Y的生命周期结束的时候,X依然引用的Y,这时候,垃圾回收器是不会回收对象Y的;如果对象X还引用着生命周期比较短的A/B/C,对象A又引用着对象a、b、c这样就可能造成大量无用的对象不能被回收,进而占据了内存资源,造成内存泄露,直至内存溢出。
内存泄露和内存溢出的关系:
内存泄露的增多最终会导致内存溢出。
泄露的分类
经常发生:发生内存泄露的代码被多次执行,每执行一次,泄露一块内存。
偶然发生:在某些特定情况下才会发生。
一次性:发生内存泄露的方法只会执行一次。
隐式泄露:一直站着内存不释放,直到执行结束;严格的说这个不算泄露,因为内存释放掉了,但是如果执行时间特别长,也会导致内存耗尽。
Java内存泄露的8种情况:
1.静态集合类
静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前不能被释放,从而造成内存泄露。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
public class MemoryLeak{
static List list = new ArrayList();
public void oomTest(){
Object obj = new Object();
List.add(obj);
}
}
2.单例模式
单例模式,和静态集合导致内存泄露的原因类似,但因为单例的静态特性,它的生命周期和JVM的生命周期一样长,所以如果单例对象持有外部对象的引用,那么这个外部对象也不会回收,那么就会造成内存泄露。
3.内部类持有外部类
内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象。这个内部类对象被长期持有了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
4.各种连接,如数据库连接、网络连接和IO连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有在连接被关闭后,垃圾回收器才会回收相应的对象。否则,如果在数据库访问过程中,对Connection、Statement或ResultSet不显性的关闭,将会造成大量对象无法回收,从而引起内存泄露。
public static void main(String args[]){
try{
Connection conn = null;
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("url", "", "");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("...");
}catch(Exception e){//异常日志
}finally{
//关闭结果集 Statement
//关闭声明的对象 ResultSet
//关闭连接 Connection
}
}
5.变量不合理的作用域
变量不合理的作用域。一般而言,一个变量定义的范围可能大于器使用范围,很有可能会造成内存泄露。另一方面,如果没有即时地把对象设置为null,很有可能导致内存泄露的发生。
public class UsingRandom{
private String msg;
public void receiveMsg(){
readFromNet(); //从网络中接受数据保存到msg中
saveDB();//把数据保存到数据库中
}
}
如上面这个伪代码,通过readFromNet方法把接受的消息保存在变量msg中,然后调用saveDB方法把msg中的内容保存到数据库中,此时msg已经没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄露。
实际上这个msg变量可以放在receiveMsg内部,当方法使用完,那么msg的生命周期也就结束,此时就可以回收了。还有一种方法,就是在使用哇msg后,把msg设为null,这样垃圾回收器也会会后msg的内存空间。
6.改变哈希值
改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了,在这种情况下,即使contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将找不到对象的结果。这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。
这也是为什么String会被设置为不可变类型,我们可以放心把String存入HashSet,或者把String 当做HashMap的key值;当我们想把自己定义的类保存到散列表的时候,需要保证对象的hashcode不可变。
7.缓存泄露
内存泄露的另一个常见来源是缓存,一旦你把对象放到缓存中,就很容易被遗忘。比如:之前项目再一次上线的时候,应用启动奇慢直至夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。
对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。
8.内存泄露的第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示取消,那么就会积聚。需要确保回调立即被当做垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存为WeakHashMap中的键。
补充2: 支持使用OQL语言查询对象信息
MAT支持一种类似于SQl的查询语言OQL(Object Query Language)。OQL使用类SQL语法,可以在堆中进行对象的查找和筛选。
1.SELECT子句
在MAT中,Select子句的格式和SQL基本一致,用于指定要显示的列。Select子句中可以使用“ * ”,查看结果对象的引用实例(相当于outgoing references)。
SELECT * FROM java.util.Vector v
使用“OBJECTS”关键字,可以将返回结果集中的项以对象的形式显示。
SELECT objects v.elementData FROM java.util.Vector v
SELECT OBJECTS s.value FROM java.lang.String s
在Select子句中,使用“AS RETAINED SET”关键字可以得到所有对象的保留集。
SELECT AS RETAINED SET * FROM com.atguigu.mat.Student
"DISTINCT"关键字用于在结果集中去除重复对象。
SELECT DISTINCT OBJECTS classof(s) FROM java.lang.String s
2.FROM子句
From子句用于指定范围查询,它可以指定类名、正则表达式或者地址对象。
SELECT * FROM java.lang.String s
下例使用正则表达式,限定搜索范围,输出所有的com.atguigu包下的所有实例
SELECT * FROM "com\.atguigu\..*"
也可以直接使用类的地址进行搜索。使用类的地址的好处是可以区分被不同ClassLoader加载的同一种类型。
select * from 0x37a0b4d
3.WHERE子句
Where子句用于指定OQL的查询条件。OQL查询将只返回满足where子句指定条件的对象。Where子句的格式和传统的SQL极为相似。
下例返回长度大于10的char数组。
SELECT * FROM char[] s WHERE s.@length>10
下例返回包含“java”子字符串的所有字符串,使用“LIKE”操作符,“LIKE”操作符的操作参数为正则表达式。
SELECT * FROM java.lang.String s WHERE toString(s) LIKE ".*java.*"
下例返回所有value域不为null的字符串,使用“=”操作符。
SELECT * FROM java.lang.String s WHERE s.value!=null
where子句支持多个条件的AND、OR运算。下例返回数组长度大于15,并且深堆大于1000字节的所有Vector对象。
SELECT * FROM java.util.Vector v WHREE v.elementData.@length>15 AND v.@retainedHeapSize>1000
4.内置对象和方法
OQL中可以访问堆内对象的属性,也可以访问堆内代理对象的属性。访问堆内对象的属性时,格式如下:
[ <alias>. ] <field>. <field>. <field>
其中alias为对象名称。
访问java.io.File对象的path属性,并进一步访问path的value属性:
SELECT toString(f.path.value) FROM java.io.File f
下例显示了String对象的内容、Objectid和ObjectAddress。
SELECT s.toString (), s.@objectId, s.@objectAddress FROM java.lang.String s
下例显示java.util.Vector内部数组的长度。
SELECT v.elementData.@length FROM java.util.Vector v
下例显示了所有的java.util.Vector对象及其子类型
select * from INSTANCEOF java.util.Vector
05-JProfiler
1.基本概述
介绍:在运行Java的时候有时候想测试运行时占用的内存情况,这就需要使用测试工具查看了。在Eclipse里面有Eclipse Memory Analyzer tool(MAT)插件可以测试,而在IDEA中也有这么一个插件,就是JProfiler。
JProfiler是由ej-technologies公司开发的一款Java应用性能诊断工具。功能强大,但是收费。
特点:
- 使用方便、界面操作友好(简单且强大)
- 对被分析的应用影响小(提供模板)
- CPU,Thread,Memory分析功能尤其强大
- 支持对jdbc,noSql,jsp,servlet,socket等进行分析
- 支持多种模式(离线、在线)的分析
- 支持监控本地、远程的JVM
- 跨平台,拥有多种操作系统的安装版本
主要功能:
1.方法调用:对方法调用的分析可以帮助你了解应用程序正在做什么,并找到提高性能的方法
2.内存分配:通过分析堆上对象、引用链和垃圾收集能帮助您修复内存泄露问题,优化内存使用
3.线程和锁:JProfiler提供多种针对线程和锁的分析视图帮助您发现多线程问题
4.高级子系统:许多性能问题都发生在更高的语义级别上。例如,对于JDBC调用,您可能希望找出执行最慢的SQL语句。JProfiler支持对这些子系统进行集成分析。
3.具体使用
1.数据采集方式
JProfiler数据采集方式分为两种:Sampling(样本采集)和Instrumentation(重构模式)
Instrumentation:这是JProfiler全功能模式。在class加载之前,JProfiler把相关功能代码写入到需求分析的class的bytecode中,对正在运行的jvm有一定影响。
- 优点:功能强大。在此设置中,调用堆栈信息是准确的。
- 缺点:若要分析的class较多,则对应用的性能影响较大,CPU开销可能很高(取决于Filter的控制)。因为使用此模式一般配合Filter使用,只对特定的类或包进行分析。
Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来。
- 优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)
- 缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)
- 注:JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是JProfiler的数据采集类型。
2.遥感监测 Telemetries(查看JVM的运行信息)
- 整体视图:Overview:显示堆内存、cpu、线程以及GC等活动视图
- 内存 Memory:显示一张关于内存活动变化的活动时间表
- 记录的对象 Recorded objects:显示一张关于活动对象与数组的图表的活动时间表。
- 记录吞吐量 Record Throughput:显示一段时间累计的JVM生产和释放的活动时间表。
- 垃圾回收活动 GC Activity:显示一张关于垃圾回收活动的活动时间表。
- 类 Classes:显示一个与已装载类的图表的活动时间表。
- 线程 Threads:显示一个与动态线程图表的活动时间表
- CPU负载 CPU Load:显示一段时间中的CPU的负载图表
3.内存视图 Live Memory
Live Memory内存剖析:class/class instance的相关信息。例如对象的个数,大小,对象创建的方法执行栈,对象创建的热点。
- All Objects 所有对象
显示所有加载的类的列表和在堆上分配的实例数。只有java 1.5(JVMTI)才会显示此视图。
- Record Objects 记录对象
查看特定时间段对象的分配,并记录分配的调用堆栈
- Allocation Call Tree
显示一颗请求树或者方法、类、包或对已选择类带有注释的分配信息的J2EE组件。
- Allocation Hot Spots
显示一个列表,包括方法、类、包或分配已选类的J2EE组件。你可以标注当前值并显示差异值。对于每一个热点都可以显示它的跟踪记录树。
- Class Tracker
类跟踪视图可以包含任意数量的图表,显示选定的类和包实例与时间。
分析:内存中对象的情况
频繁创建的Java对象:死循环、循环次数过多
存在大对象:读取文件时,byte【】应该边读边写。-->如果长时间不写出的话,导致byte【】过大
存在内存泄露。
4.堆遍历 heap walker
类 Classes
显示所有类和他们的实例,可以右击具体的类“Used Selected Instance”实现进一步跟踪。
分配 Allocation
为所有记录对象显示分配树和分配热点。
索引 References
为单个对象和“显示到垃圾回收根目录的路径”提供索引图的显示功能。还能提供合并入输入视图和输出视图的功能。
时间 Time
显示一个对已记录对象的解决时间的柱状图。
检查 Inspections
显示了一个数量操作,将分析当前对象集在某种条件下的子集,实质是一个筛选的过程。
图表 Graph
你需要在references视图和biggest视图手动添加对象到图表,他可以显示对象传入和传出引用,能方便的找到垃圾收集器根源。
ps:在工具栏点击“GO TO START”可以使堆内存重新计数,也就是回到初始状态。
5.cpu视图 cpu views
JProfiler 提供不同的方法记录访问树以优化性能和细节。线程或者线程组以及线程状况可以被所有的视图选择。所有的视图都可以聚集到方法、类、包或者JeEE组件等不同层上。
访问树 Call Tree
显示一个积累的自顶向下的树,树中包含所有在JVM中已记录的访问队列。JDBC,JMS和JNDI服务请求都被注释在请求树中。请求树可以根据Serlvet和JSP对URL的不同需要进行拆分。
热点 Hot Spots
显示消耗时间最多的方法列表。对每个热点都能够显示回溯树。该热点可以按照方法请求,JDBC,JMS和JNDI服务请求以及按照URL请求来计算。
访问图 Call Graph
显示一个从已选方法、类、包或J2EE组件开始的访问队列的图。
方法统计 Method Statistic
显示一段时间记录的方法的调用时间细节。
6.线程视图 threads
JProfiler通过对线程历史的监控判断器运行状态,并监控是否有线程阻塞产生,还能将一个线程所管理的方法以树状形式呈现。对线程剖析 。
线程历史 Thread History
显示一个与线程活动和线程状态在一起的活动时间表。
线程监控 Thread Monitor
显示一个列表,包括所有的线程活动以及他们目前的活动状况。
线程转储 Thread Dumps
显示所有线程的堆栈跟踪。
线程分析主要关心三个方面:
1、web容器的线程最大数。比如:Tomcat的线程容量应该略大于最大并发数。
2、线程阻塞
3、线程死锁
06-Arthas
1.基本概述:
(1)背景:jvisualvm和JProfiler优点是可以在图形的界面上看到各维度的性能数据,使用者根据这些数据进行综合分析,然后判断哪里出了性能问题。但是这两款工具也有个缺点,都必须在服务端项目进程中连接到项目进程,获取相关的数据。这样就会带来一些不便,比如线上的网络环境是隔离的,本地的监控工具根本连不上线上环境。并且类似于JProfiler这样的商业工具,是需要付费的。
那么又没有一款工具不需要远程连接,也不要配置参数,同时提供了丰富的性能监控数据呢?
今天给大家介绍一款阿里巴巴开源的性能分析神器Arthas(阿尔萨斯)
(2)概述:Arthas(阿尔萨斯)是alibaba开源的java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。
Arthas支持JDK 6+,支持Linux、Mac、Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
- 这个类从哪个jar包加载的?为什么会报类相关的Exception?
- 我改的代码为什么没有执行到?难道我没commit?分支搞错了?
- 遇到问题无法线上debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行情况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
(3)基于哪些工具开发而来?
- greys-anatomy:Arthas代码基于Greys二次开发而来,非常感谢Greys之前所有的工作,以及Greys原作者对Arthas提出的意见和建议!
- termd:Arthas的命令行实现基于termd开发,是一款优秀的命令行开发框架,感谢termd提供了优秀的框架。
- crash:Arthas的文本渲染功能基于crash中的文本渲染功能开发,可以从这里看到源码,感谢crash在这方面所做的优秀工作。
- cli:Arthas的命令行界面基于vert.x提供的cli库进行开发,感谢vert.x在这方面做的优秀工作。
- complier Arthas里的内存编译器代码来源
- Apache Common Net Arthas里的Telent Client代码来源
- JavaAgnet:运行在main方法前的拦截器,它内定的方法名叫premain,也就是说先执行premain方法,再执行main方法。
- ASM:一个通用的Java字节码操作盒分析框架。他可以用于修改现有的类或者直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从它们构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的功能,但是主要关注性能。因为它被设计和实现得尽可能小和快,所以非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)
07- Java Mission Control
1.历史
在Oracle收购Sun之前,Oracle的 JRockit虚拟机提供了一款叫做JRockit Mission Control的虚拟机诊断工具。在Oracle收购sun之后,Oracle公司同时拥有了Sun HotSpot和JRockit两款虚拟机。根据Oracle对于Java的战略,在今后的发展中,会将JRockit的优秀特性移植到HotSpot上。其中,一个重要的改进就是在Sun的JDK中加入了JRockit的支持。
在Oracle JDK 7u40之后,Mission Control这款工具已经绑定在Oracle JDK中发布。
自Java 11 开始,本节介绍的JFR已经开源。但在之前的Java版本,JFR属于Commercial Feature,需要通过Java虚拟机参数-XX:+UnlockCommercialFeatures开启。
3.概述
Java Mession Control(简称JMC),Java官方提供的性能强劲的工具。是一个用于对Java应用程序进行管理、监视、概要分析和故障排除的工具套件。
它包含一个GUI客户端,以及众多用来收集Java虚拟机性能数据的插件,如JMX Console(能够访问用来存放虚拟机各个子系统运行数据的MXBeans),以及虚拟机内置的高效profiling工具Java Flight Recorder(JFR)。
JMC的另一个优点就是:采用取样,而不是传统的代码移植技术,对应用性能的硬性非常非常小,完全可以开着JMC来做压测(唯一的影响可能是full gc多了)。
4.功能:实时监控JVM的运行是状态
如果是远程服务器,使用前要开JMX。
-Dcom.sun.management.jmxremote.port=${YOUR PORT}
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.authenticate= false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=${YOUR HOST/IP}
文件 --》 连接-- 》 创建连接,填入上面JMX参数的host 和port
5.java Flight Recorder:是JMC的一个组件。能够以极低的性能开销收集Java虚拟机的性能数据。
JFR的性能开销很小,在默认配置下平均低于1%。与其他工具相比,JFR能够直接访问虚拟机内的数据,并且不会影响虚拟机的优化。因此,他非常适合与于生产环境下满负荷运行的Java程序。
Java Flight Recorder和JDK Mission Control共同创建了一个完整的工具链。JDK Mission Control可对Java Flight Recorder连续收集低水平和详细的运行时信息,进行高效的、详细的分析。
(1)事件类型:当启用时,JFR将记录运行过程中发生的一系列事件。包括Java层面的事件,如线程事件、锁事件,以及Java虚拟机内部事件,如新建对象、垃圾回收和即时编译事件。
按照发生时机以及持续事件来划分,JFR的事件公有四种类型,它们分别为以下四种。
1.瞬时事件(Instant Event),用户关心的是它们发生与否,例如异常、线程启动事件。
2.持续事件(Duration Event),用户关心的使他们的持续事件,例如垃圾回收事件。
3.计时事件(Timed Event),是时长超出指定阈值的持续事件。
4.取样事件(Sample Event),是周期性取样事件。
取样事件的其中一个例子就是方法取样(Method Sampling),即每隔一段时间统计各个线程的栈轨迹。如果在这些抽样的栈轨迹中存在一个反复出现的方法,那我们推测该方法是热点方法。
(2)启动方式:
方式1:使用-XX:StartFlightRecording=参数;
第一种是在运行目标Java程序时添加-XX:-StartFlightRecording=参数。
比如:下面命令中,JFR会将在Java虚拟机启动5s后(对应delay=5s)收集数据,持续20s(对应duration=20s)。当收集完毕后,JFR会将收集到的数据保存至指定的文件中(对应filename=myrecording.jfr
java
-XX:StartFlightRecording=delay= 5s,duration=20s,filename=myrecording.jfr, settings = profile MyApp
由于JFR将持续收集数据,如果不加以限制,那么JFR可能会填满硬盘所有空间。因此,我们有必要对这种模式下收集的数据进行限制。
比如:
java -XX: Start FlightRecording= maxage=10m, maxsize= 100m, name = SomeLabel MyApp
方式2:使用jcmd的JFR.*子命令;方式3:JMC的JFR插件
(3)Java Flight Recorder 取样分析:
要取样,必须先添加参数:
-XX:+UnlockCommercialFeatures
-XX:+FlightRecorder
否则:
取样时间默认1分钟,可自行按需调整,事件设置选择为profiling,然后可以设置取样profile哪些信息,比如:
- 加上对象数量的统计:Java Virtual Machine -》GC -》Detailed --》Object Count/Object Count after GC
- 方法调用采样的间隔从10ms改为1ms(但不能低于1ms,否则会影响性能了):Java Virtual Machine -》 Profiling - 》 Method Profiling Smpale/Method Sampling Information
08-其他工具
1.Flame Graphs(火焰图)
在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直观的展示cpu在程序整个生命周期过程中时间分配的工具。
可以非常直观的显示出调用栈中的cpu消耗瓶颈。
网上的关于java火焰图的讲解大部分来自于Brendan Gregg的博客。
火焰图,简单通过x轴横条宽度来度量时间指标,y轴代表线程栈的层次。
2.Tprofiler
案例:使用JDK自身提供的工具进行JVM调优可以将TPS由2.5提升到20(提升了7倍),并准确定位系统瓶颈。
系统瓶颈是:应用里静态对象是不太多、有大量业务线程在频繁创建一些生命周期很长的临时对象,代码里有问题。
那么如何在海量业务代码里准确定位这些性能代码?这里使用阿里开源工具TProfier来定位这些性能代码,成功解决掉了GC过于频繁的系统瓶颈,并最终在上次优化的基础上将TPS再次提升了4倍,即提升到100.
- Tprofiler配置部署、远程操作、日志阅读都不太复杂,操作还是很简单的。但是却能起到一针见血、立杆见影的效果,帮助我们解决了GC过于频繁的性能瓶颈。
- Tprofiler最重要的特性就是能够统计出你指定时间段内JVM的top method,这些top method 极有可能就是造成你JVM性能瓶颈的元凶。这是其他大多数JVM调优工具所不具备的,包括JRockit Mission Control。 JRockit首席开发者Marus Hirt在其私人博客《Low Overhead Method Profiler with Java MIssion Control》下的评论明确指出JRMC并不支持TOP方法统计。
3.Btrace
Java运行时追踪工具
常见的动态追踪工具有BTrace、HouseMD(该项目已经停止开发)、Greys-Anatomy(国人开发,个人开发者)、Byteman(JBoss出品),注意Java运行时追踪工具并不限于这几种,但是这几个是相对比较常用的。
BTrace是Sun Kenai云计算开发平台下的一个开源项目,旨在为java提供安全可靠的动态追踪分析工具。先看一下BTrace的官方定义:
BTrace is a safe, dynamic tracing tool for the Java platform. BTrace can be used to dynamically trace a running Java program (similar to DTrace for OpenSolaris application and OS). BTrace dynamically instruments the classes of the target application to inject tracing code ("bytecode tracing").
简洁明了,大意是一个Java平台的安全的动态追踪工具。可以用来动态的追踪一个运行的Java程序。BTrace动态调整目标应用程序的类以注入跟踪代码(”字节码跟踪“)。