文章目录
第1章、工具概述
- 使用命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们存在下列局限:
- 1、无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)
- 2、要求用户登录到目标 Java应用所在的宿主机上,使用起来不是很方便
- 3、分析数据通过终端输出,结果展示不够直观
- 图形化综合诊断工具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。能够以极低的性能开销收集]ava虚拟机的性能数据
- jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等
- 第三方工具
- MAT:MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
- Eclipse的插件形式
- JProfiler:商业软件,需要付费。功能强大
- Arthas:Alibaba开源的Java诊断工具
- Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息
- MAT:MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
第2章、jConsole
1、基本概述
- jconsole:
- 从Java5开始,在JDK中自带的java监控和管理控制台
- 用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具
- 官方地址:https://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.html
2、启动
- 在jdk安装目录中找到jconsole.exe,双击该可执行文件就可以
- 打开DOS窗口,直接输入jconsole就可以了
3、三种连接方式
【1】Local
-
使用JConsole连接一个正在本地系统运行的JVM,并且执行程序的和运行JConsole的需要是同一个用户。JConsole使用文件系统的授权通过RMI连接起链接到平台的MBean的服务器上。这种从本地连接的监控能力只有Sun的JDK具有
-
注意:本地连接要求 启动jconsole的用户 和 运行当前程序的用户 是同一个用户
-
具体操作如下:
-
1、在DOS窗口中输入jconsole
-
2、在控制台上填写相关信息
-
3、选择“不安全的连接”
-
4、进入控制台页面
-
【2】Remote
- 使用URL通过RMI连接器连接到一个JMX代理,service:jmx:rmi:///jndi/rmi://hostName:portNum/jmxrmi
- JConsole为建立连接,需要在环境变量中设置mx.remote.credentials来指定用户名和密码,从而进行授权
【3】Advanced
- 使用一个特殊的URL连接JMX代理。一般情况使用自己定制的连接器而不是RMI提供的连接器来连接JMX代理,或者是一个使用JDK1.4的实现了JMX和JMX
4、主要作用
-
1、概览
-
2、内存
-
3、根据线程检测死锁
- 4、线程
-
5、VM 概要
第3章、Visual VM
- jvisualvm和visual vm的区别:
- visual vm是单独下载的工具,然后将visual vm结合到jdk中就变成了jvisualvm,仅仅是添加了一个j而已,这个j应该是java的用处,所以说jvisualvm其实就是visual vm
1、基本概述
- Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具
- 它集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole
- 在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM 在JDK/bin目录下)即:它完全免费
- 此外,Visual VM也可以作为独立的软件安装:
- https://visualvm.github.io/index.html
- 使用:
- 在jdk安装目录中找到jvisualvm.exe,然后双击执行即可
- 打开DOS窗口,输入jvisualvm就可以打开该软件
2、插件的安装
-
Visual VM的一大特点是支持插件扩展,并且插件安装非常方便。我们既可以通过离线下载插件文件*.nbm,然后在Plugin对话框的已下载页面下,添加已下载的插件。也可以在可用插件页面下,在线安装插件(这里建议安装上:VisualGC)
-
插件地址: https://visualvm.github.io/pluginscenters.html
-
IDEA安装VisualVM Launcher插件
- Preferences --> Plugins -->搜索VisualVM Launcher,安装重启即可。
-
1、在IDEA中搜索VisualVM Launcher插件并安装:
-
2、重启IDEA,然后配置该插件
-
3、使用两种方式来运行程序
-
4、运行效果
- 打开jvisualvm界面,只是不需要我们手动打开jvisualvm而已
3、连接方式
- 1、本地连接
- 监控本地Java进程的CPU、类、线程等
- 2、远程连接
- 1-确定远程服务器的ip地址
- 2-添加JMX(通过JMX技术具体监控远程服务器哪个Java进程)
- 3-修改bin/catalina.sh文件,连接远程的tomcat
- 4-在…/conf中添加jmxremote.access和jmxremote.password文件
- 5-将服务器地址改成公网ip地址
- 6-设置阿里云安全策略和防火墙策略
- 7-启动tomcat,查看tomcat启动日志和端口监听
- 8-JMX中输入端口号、用户名、密码登录
4、主要功能
-
1、生成/读取堆内存快照
-
【1】生成堆内存快照
-
方式1
-
方式2
-
注意:
-
生成堆内存快照如下图:
-
快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:
-
-
-
【2】装入堆内存快照
-
-
2、查看JVM参数和系统属性
-
3、查看运行中的虚拟机进程
-
4、生成/读取线程快照
-
【1】生成线程快照
-
方式1
-
注意:快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:
-
-
【2】装入线程快照
-
-
5、程序资源的实时监控
-
6、其他功能
- JMX代理连接
- 远程环境监控
- CPU分析和内存分析
第4章、Eclipse MAT
1、基本概述
- MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况
- MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便
- 官方下载地址: https://www.eclipse.org/mat/downloads.php
- 单独使用,解压即可用
2、获取堆dump文件
【1】dump文件内存
- MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息
- 这些内存信息包括:
- 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值
- 所有的类信息,包括classloader、类名称、父类、静态变量等
- GCRoot到所有的这些对象的引用路径
- 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)
【2】说明
- MAT不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如Sun,HP,SAP所采用的 HPROF二进制堆存储文件,以及工IBM的 PHD 堆存储文件等都能被很好的解析
- 最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现
【2】获取dump文件
- 方法1:使用jmap工具生成,可以生成任意一个java进程的dump文件
- 方法2:通过配置VM参数生成:
- 选项"-XX:+HeapDumpOnoutOfMemoryError”或"-XX:+HeapDumpBeforeFullGC"
- 选项"-XX:HeapDumpPath"所代表的含义就是当程序出现OutOfMemory时,将会在相应的目录下生成一份dump文件。如果不指定选项“-XX:HeapDumpPath”则在当前目录下生成dump文件
- 对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用jmap+MAT工具是最常见的组合
- 方法3:使用VisualVM可以导出堆dump文件
- 方法4:使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动Java程序中导出堆快照。该功能将借助jps列出当前正在运行的Java进程,以供选择并获取快照:
3、分析堆dump文件
【1】histogram
- 展示了各个类的实例数目以及这些实例的Shallow heap或者Retained heap的总和
【2】thread overview
- 查看系统中的Java线程
- 查看局部变量的信息
【3】获得对象互相引用的关系
- with outgoing references
- with incoming references
【4】浅堆与深堆
-
浅堆(shallow heap)
- 浅堆(Shallow Heap)是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同,对象的大小可能会陶8字节进行对齐
- 以String为例:2个int值共占8字节,对象引用占用4字节,对象头8字节,合计20字节,向8字节对齐,故占24字节。(jdk7中)
- 这24字节为String对象的浅堆大小。它与String的value实际取值无关,无论字符串长度如何,浅堆大小始终是24字节
- 对象头代表根据类创建的对象的对象头,还有对象的大小不是可能向8字节对齐,而是就向8字节对齐
-
深堆(retained heap)
-
深堆(Retained Heap)指对象的保留集中所有的对象的浅堆大小之和
-
保留集(Retained Set):
- 对象A的保留集指当对象A被垃圾回收后,可以被释放的所有的对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象A所持有的对象的集合
-
注意:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间
-
-
注意:当前深堆大小 = 当前对象的浅堆大小 + 对象中所包含对象的深堆大小
-
补充:对象实际大小
-
对象的实际大小定义为一个对象所能触及的所有对象的浅堆大小之和,也就是通常意义上我们说的对象大小。与深堆相比,似乎这个在日常开发中更为直观和被人接受,但实际上,这个概念和垃圾回收无关
-
下图显示了一个简单的对象引用关系图,对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大小只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小为A与D之和,由于对象C还可以通过对象B访问到,因此不在对象A的深堆范围内
-
-
案例
/** * 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。 * 它由三个部分组成:Student、WebPage和StudentTrace三个类 * * -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=c:\code\student.hprof */ public class StudentTrace { static List<WebPage> webpages = new ArrayList<WebPage>(); public static void createWebPages() { for (int i = 0; i < 100; i++) { WebPage wp = new WebPage(); wp.setUrl("http://www." + Integer.toString(i) + ".com"); wp.setContent(Integer.toString(i)); webpages.add(wp); } } public static void main(String[] args) { createWebPages();//创建了100个网页 //创建3个学生对象 Student st3 = new Student(3, "Tom"); Student st5 = new Student(5, "Jerry"); Student st7 = new Student(7, "Lily"); for (int i = 0; i < webpages.size(); i++) { if (i % st3.getId() == 0) st3.visit(webpages.get(i)); if (i % st5.getId() == 0) st5.visit(webpages.get(i)); if (i % st7.getId() == 0) st7.visit(webpages.get(i)); } webpages.clear(); System.gc(); } } class Student { private int id; private String name; private List<WebPage> history = new ArrayList<>(); public Student(int id, String name) { super(); this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<WebPage> getHistory() { return history; } public void setHistory(List<WebPage> history) { this.history = history; } public void visit(WebPage wp) { if (wp != null) { history.add(wp); } } } class WebPage { private String url; private String content; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
- 结论:
- elementData数组的浅堆是80个字节,而elementData数组中的所有WebPage对象的深堆之和是1208个字节,所以加在一起就是elementData数组的深堆之和,也就是1288个字节
- 解释:
- “elementData数组的浅堆是80个字节”,其中15个对象一共是60个字节,对象头8个字节,数组对象本身4个字节,这些的和是72个字节,然后总和要是8的倍数,所以“elementData数组的浅堆是80个字节”
- “WebPage对象的深堆之和是1208个字节”,一共有15个对象,其中0、21、42、63、84、35、70不仅仅是7的倍数,还是3或者5的倍数,所以这几个数值对应的i不能计算在深堆之内,这15个对象中大多数的深堆是152个字节,但是i是0和7的那两个深堆是144个字节,所以(13152+1442)-(6*152+144)=1208,所以这也印证了我上面的话,即“WebPage对象的深堆之和是1208个字节”
- 因此“elementData数组的浅堆80个字节”加上“WebPage对象的深堆之和1208个字节”,正好是1288个字节,说明“elementData数组的浅堆1288个字节”
- 结论:
【5】支配树
-
支配树(Dominator Tree)支配树的概念源自图论
-
MAT提供了一个称为支配树(Dominator Tree)的对象图。支配树体现了对象实例间的支配关系。在对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B。如果对象A是离对象B最近的一个支配对象,则认为对象A为对象B的直接支配者。支配树是基于对象间的引用图所建立的,它有以下基本性质:
- 对象A的子树(所有被对象A支配的对象集合)表示对象A的保留集(retained set),即深堆
- 如果对象A支配对象B,那么对象A的直接支配者也支配对象B
- 支配树的边与对象引用图的边不直接对应。
-
如下图所示:左图表示对象引用图,右图表示左图所对应的支配树
第5章、关于内存泄露的那点事
1、内存泄露理解与分析
-
何为内存泄露(memory leak)
- 可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)
- 还在被使用,但不需要的对象
-
内存泄漏( memory leak)的理解
- 严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏
- 实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”
-
内存泄漏与内存溢出的关系:
- 内存泄漏( memory leak )
- 申请了内存用完了不释放,比如一共有1024M的内存,分配了512M的内存一直不回收,那么可以用的内存只有512M 了,仿佛泄露掉了一部分
- 内存溢出(out of memory)
- 申请内存时,没有足够的内存可以使用
- 内存泄漏( memory leak )
-
泄漏的分类
- 经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存
- 偶然发生:在某些特定情况下才会发生
- 一次性:发生内存泄露的方法只会执行一次
- 隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽
2、Java中内存泄露的8种情况
- 1-静态集合类
- 静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏
- 简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收
- 2-单例模式
- 单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM 的生命周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏
- 3-内部类持有外部类
- 内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象
- 这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏
- 4-各种连接,如数据库连接、网络连接和IO连接等
- 在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象
- 否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏
- 5-变量不合理的作用域
- 变量不合理的作用域:简言之,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生
- 6-改变哈希值
- 改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了
- 否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏
这也是 String为什么被设置成了不可变类型,我们可以放心地把 String存入 HashSet,或者把String当做HashMap的key值 - 当我们相把自己定义的类保存到散列表的时候.需要保证对象的hashCode不可亦。
- 7-缓存泄露
- 内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据
- 对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。
- 8-监听器和回调
- 内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚
- 需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为weakHashMap中的键
第6章、关于OQL语言(了解)
- MAT支持一种类似于SQL的查询语言OQL(Object Query Language)。OQL使用类SQL语法,可以在堆中进行对象的查找和筛选
1、SELECT子句
- 在MAT中,Select子句的格式与SQL基本一致,用于指定要显示的列。Select子句中可以使用“*”,查看结果对象的引用实例(相当于outgoing references)
- SELECT * FROM java.util.Vector v
- 使用“OBECTS”关键字,可以将返回结果集中的项以对象的形式显示
- SELECT objects v.elementData FROM java.util.Vector v
- SELECT 0BJECTS 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.example包下所有类的实例
SELECT * FROM "com\.example\..*"
-
也可以直接使用类的地址进行搜索。使用类的地址的好处是可以区分被不同ClassLoader加载的同一种类型
- select * from Ox37a8b4d
3、WHERE子句
- where子句用于指定OQL的查询条件。oQL查询将只返回满足where子句指定条件的对象
- 下例返回长度大于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 WHERE v.elementData.@length>15 AN D v.@retainedHeapsize>1000
第7章、JProfiler
1、基本概述
-
简介
-
在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在eclipse里面有(MAT)插件可以测试,而在IDEA中也有一个插件,就是JProfiler
-
JProfiler是由 ej-technologies公司开发的一款Java应用性能诊断工具。功能强大,但是收费
-
官网下载地址: https://www.ej-technologies.com/products/jprofiler/overview.html
-
-
特点
- 使用方便、界面操作友好(简单且强大)
- 对被分析的应用影响小(提供模板)
- CPU,Thread,Memory分析功能尤其强大
- 支持对jdbc,noSql,jsp,servlet,socket等进行分析支持多种模式(离线,在线)的分析
- 支持监控本地、远程的JVM
- 跨平台,拥有多种操作系统的安装版本
-
主要功能
- 1-方法调用
- 对方法调用的分析可以帮助了解应用程序正在做什么,并找到提高其性能的方法
- 2-内存分配
- 通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄露问题,优化内存使用
- 3-线程和锁
- JProfiler提供多种针对线程和锁的分析视图助您发现多线程问题
- 4-高级子系统
- 许多性能问题都发生在更高的语义级别上
- 例如,对于JDBC调用,您可能希望找出执行最慢的SQL语句
- JProfiler支持对这些子系统进行集成分析
- 1-方法调用
2、安装与配置
- 下载地址https://www.ej-technologies.com/download/jprofiler/version_10e
【1】JProfiler中配置IDEA
-
1、IDE Integrations
-
2、选择合适的IDE版本
-
3、开始集成
-
4、正式集成
-
5、集成成功
【2】IDEA集成JProfiler
3、如何使用
-
常见操作:
-
1、Starter Center
- 如果程序已经保存了Quick Attach,那么即使下次程序运行的时候没有启动JProfiler,而我们启动JProfiler,然后找到该Quick Attach中的对应位置,点击就可以运行了
-
2、垃圾回收
-
3、标记
-
4、手动刷新
-
5、Live memory中的Recorded Objects可以查看对象信息,根据View中的Change Liveness Mode来更改查看的对象类型
- 如果通过Telemetries中的Memory看到垃圾回收之后内存占用还是越来越多,那就需要注意内存泄露问题了,这个时候我们可以查看Live memory中的Recorded Objects中的对象信息,可以先查看Live Objects中的,也就是存活的对象,然后在查看Garbage Collected Objects,如果某对象只在Live Objects中出现,但是没有在Garbage Collected Objects中出现,那么说明该对象就没有进行垃圾回收,即该对象有可能造成内存泄露
-
6、保存堆快照dump文件
4、具备的功能
【1】数据采集方式
-
JProfier数据采集方式分为两种:Sampling(样本采集)和Instrumentation(重构模式)
-
Instrumentation:这是JProfiler全功能模式。在class加载之前,JProfier把相关功能代码写入到需要分析的class的bytecode中,对正在运行的jvm有一定影响。
-
优点:功能强大。在此设置中,调用堆栈信息是准确的。
-
缺点:若要分析的class较多,则对应用的性能影响较大,CPU开销可能很高(取决于Filter的控制)。因此使用此模式一般配合Filter使用,只对特定的类或包进行分析
-
-
Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来。
- 优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)
- 缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)
-
注:JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是JProfiler的数据采集类型
【2】遥感监测 Telemetries
【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:类跟踪视图可以包含任意数量的图表,显示选定的类和包的实例与时间
-
-
注意:
- All Objects后面的Size大小是浅堆大小
- Record Objects在判断内存泄露的时候使用,可以通过观察Telemetries中的Memory,如果里面出现垃圾回收之后的内存占用逐步提高,这就有可能出现内存泄露问题,所以可以使用Record Objects查看,但是该分析默认不开启,毕竟占用CPU性能太多
【4】堆遍历 heap walker
- 如果通过内存视图 Live Memory已经分析出哪个类的对象不能进行垃圾回收,并且有可能导致内存溢出,如果想进一步分析,我们可以在该对象上点击右键,选择Show Selection In Heap Walker,如下图:
- 之后进行溯源,操作如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yQ22mNX5-1644762465137)(image-20220111183038353.png)]
- 查看结果,并根据结果去看对应的图表:
- 以下是图表的展示情况:
【5】cpu视图 cpu views
-
JProfiler 提供不同的方法来记录访问树以优化性能和细节。线程或者线程组以及线程状况可以被所有的视图选择。所有的视图都可以聚集到方法、类、包或J2EE组件等不同层上
-
访问树 Call Tree:显示一个积累的自顶向下的树,树中包含所有在JVM中已记录的访问队列。JDBC,JMS和JNDI服务请求都被注释在请求树中。请求树可以根据Servlet和JSP对URL的不同需要进行拆分。
-
热点 Hot Spots:显示消耗时间最多的方法的列表。对每个热点都能够显示回溯树。该热点可以按照方法请求,JDBC,JMS和JNDI服务请求以及按照URL请求来进行计算。
-
访问图 Call Graph:显示一个从已选方法、类、包或J2EE组件开始的访问队列的图。
-
方法统计 Method Statistis:显示一段时间内记录的方法的调用时间细节
-
-
1、记录方法统计信息
-
2、方法统计
-
3、具体分析
【6】线程视图 threads
-
JProfiler通过对线程历史的监控判断其运行状态,并监控是否有线程阻塞产生,还能将一个线程所管理的方法以树状形式呈现。对线程剖析
-
线程历史 Thread History:显示一个与线程活动和线程状态在一起的活动时间表
-
线程监控 Thread Monitor:显示一个列表,包括所有的活动线程以及它们目前的活动状况
-
线程转储 Thread Dumps:显示所有线程的堆栈跟踪
-
-
线程分析主要关心三个方面:
-
1、web容器的线程最大数。比如:Tomcat的线程容量应该略大于最大并发数
-
2、线程阻塞
-
3、线程死锁
-
-
具体操作
-
1、查看线程运行情况
-
2、新建线程dump文件
-
【7】监视器&锁 Monitors&locks
-
所有线程持有锁的情况以及锁的信息。观察JVM的内部线程并查看状态:
-
死锁探测图表 Current Locking Graph:显示JVM中的当前死锁图表
-
目前使用的监测器 Current Monitors:显示目前使用的监测器并且包括它们的关联线程
-
锁定历史图表 Locking History Graph:显示记录在JVM中的锁定历史
-
历史检测记录 Monitor History:显示重大的等待事件和阻塞事件的历史记录
-
监控器使用统计 Monitor Usage Statistics:显示分组监测,线程和监测类的统计监测数据
-
5、案例
public class MemoryLeak {
public static void main(String[] args) {
while (true) {
ArrayList beanList = new ArrayList();
for (int i = 0; i < 500; i++) {
Bean data = new Bean();
data.list.add(new byte[1024 * 10]);//10kb
beanList.add(data);
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Bean {
int size = 10;
String info = "hello,atguigu";
static ArrayList list = new ArrayList();
}
- 通过JProfiler来看,如下图:
-
可以看到内存一直在往上涨,就是没有下降的趋势,说明这肯定有问题,过不了多久就会出现OOM,然后来到Live memory中,先标记看一下到底是哪些对象在进行内存增长,等一会看看会不会触发垃圾回收,如果不触发的话,我们自己来触发垃圾回收,之后观察哪些对象没有被回收掉,如下:
-
点击Mark Current,发现有些对象在持续增长,然后点击了一下Run GC,结果如下所示:
- 可以看出byte[]没有被回收,说明它是有问题的,点击Show Selection In Heap Walker,如下:
- 然后查看该对象被谁引用,如下:
- 结果如下:
- 可以看出byte[]来自于Bean类是的list中,并且这个list是ArrayList类型的静态集合,所以找到了:static ArrayList list = new ArrayList()
- 发现list是静态的,不妥,因为我们的目的是while结束之后Bean对象被回收,并且Bena对象中的所有字段都被回收,但是list是静态的,那就是类的,众所周知,类变量随类而生,随类而灭,因此每次我们往list中添加值,都是往同一个list中添加值,这会造成list不断增大,并且不能回收,所以最终会导致OOM
第8章、Arthas
1、基本概述
【1】背景
- jvisualvm 与 JProfiler
- 优点:可以图形界面上看到各维度的性能数据,使用者根据这些数据进行综合分析,然后判断哪里出现了性能问题
- 缺点:
- 都必须在服务端项目进程中配置相关的监控参数。然后工具通过远程连接到项目进程,获取相关的数据
- 比如:线上环境的网络是隔离的,本地的监控工具根本连不上线上环境
【2】概述
-
Arthas是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控VM状态
-
Arthas支持JDK6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断
-
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
-
这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
-
我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
-
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
-
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
-
是否有一个全局视角来查看系统的运行状况?
-
有什么办法可以监控到JVM的实时运行状态?
-
怎么快速定位应用的热点,生成火焰图?
-
-
官方地址:https://arthas.aliyun.com/doc/quick-start.html
2、安装与使用
- 安装方式1:
- 可以直接在Linux上通过命令下载
- 可以在官方Github 上进行下载,如果速度较慢,可以尝试国内的码云 Gitee下载
- github下载
wget https://alibaba.github.io/arthas/arthas-boot.jar
- Gitee下载
wget https://arthas.gitee.io/ arthas-boot.jar
- 安装方式2:
- 可以在浏览器直接访问https://alibaba.github.io/arthas/arthas-boot.jar,等待下载成功后,上传到Linux服务器上
- 卸载
- 在 Linux/Unix/Mac平台删除下面文件:
- rm -rf ~/ .arthas/
- rm -rf ~/logs/arthas
- windows平台直接删除user home下面的 .arthas 和 logs/arthas 目录
- 工程目录:
- arthas-agent:基于JavaAgent技术的代理bin:一些启动脚本
- arthas-boot:Java版本的一键安装启动脚本
- arthas-client:telnet client代码
- arthas-common:一些共用的工具类和枚举类
- arthas-core:核心库,各种arthas命令的交互和实现
- arthas-demo:示例代码
- arthas-memorycompiler:内存编绎器代码
- Fork from https:/lgithub.com/skalogs/skaETL/tree/master/compiler
- arthas-packaging:maven打包相关的
- arthas-site:arthas站点
- arthas-spy:编织到目标类中的各个切面
- static:静态资源
- arthas-testcase:测试
- 启动
- Arthas只是一个java程序,所以可以直接用java -jar运行
- 执行成功后,arthas提供了一种命令行方式的交互方式,arthas会检测当前服务器上的Java进程,并将进程列表展示出来,用户输入对应的编号(1、2、3、4…)进行选择,然后回车
- 方式1:
- java -jar arthas-boot.jar
- 选择进程(输入【】内编号(不是PID) 回车)
- 方式2:
- 运行时选择Java进程PID
- java -jar arthas-boot.jar [PID]
- 查看进程:jps
- 查看日志:cat ~/logs/arthas/arthas.log
- 查看帮助:java -jar arthas-boot.jar -h
- web console
- 除了在命令行查看外,Arthas目前还支持 web Console
- 在成功启动连接进程之后就已经自动启动,可以直接访问http://127.0.0.1:8563/访问
- 页面上的操作模式和控制台完全一样
- 退出
- 使用quit \ exit:退出当前客户端
使用stop \ shutdown:关闭arthas服务端,并退出所有客户端
- 使用quit \ exit:退出当前客户端
3、相关诊断指令
【1】基础指令
- help――查看命令帮助信息
- cat―—打印文件内容,和linux里的cat命令类似
- echo——打印参数,和linux里的echo命令类似
- grep—―匹配查找,和linux里的grep命令类似
- tee――复制标准输入到标准输出和指定的文件,和linux里的tee命令类似
- pwd——返回当前的工作目录,和linux命令类似
- cls――清空当前屏幕区域
- session――查看当前会话的信息
- reset——重置增强类,将被Arthas增强过的类全部还原,Arthas服务端关闭时会重置所有增强过的类
- version―—输出当前目标Java进程所加载的 Arthas版本号
- history——打印命令历史
- quit——退出当前Arthas客户端,其他Arthas客户端不受影响
- stop——关闭Arthas服务端,所有Arthas客户端全部退出
- keymap——Arthas快捷键列表及自定义快捷键
【2】JVM相关
- dashboard——当前系统的实时数据面板
- thread——查看当前 JVM 的线程堆栈信息
- jvm——查看当前 JVM 的信息
- sysprop——查看和修改JVM的系统属性
- sysenv——查看JVM的环境变量
- vmoption——查看和修改JVM里诊断相关的option
- perfcounter——查看当前 JVM 的Perf Counter信息
- logger——查看和修改logger
- getstatic——查看类的静态属性
- ognl——执行ognl表达式
- mbean——查看 Mbean 的信息
- heapdump——dump java heap, 类似jmap命令的heap dump功能
- vmtool——从jvm里查询对象,执行forceGc
【3】class/classloader相关
- sc——查看JVM已加载的类信息
- sm——查看已加载类的方法信息
- jad——反编译指定已加载类的源码
- mc——内存编译器,内存编译
.java
文件为.class
文件 - retransform——加载外部的
.class
文件,retransform到JVM里 - redefine——加载外部的
.class
文件,redefine到JVM里 - dump——dump 已加载类的 byte code 到特定目录
- classloader——查看classloader的继承树,urls,类加载信息,使用classloader去getResource
【4】monitor/watch/trace相关
- monitor——方法执行监控
- 对匹配class-pattern / method-pattern的类、方法的调用进行监控。涉及方法的调用次数、执行时间、失败率等
- monitor命令是一个非实时返回命令
- 常用参数:
- class-pattern:类名表达式匹配
- method-pattern:方法名表达式匹配
- -c:统计周期,默认值为120秒
- watch——方法执行数据观测
- 能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写groovy表达式进行对应变量的查看
- 常用参数:
- class-pattern:类名表达式匹配
- method-pattern:方法名表达式匹配
- express:观察表达式
- condition-express:条件表达式
- -b:在方法调用之前观察(默认关闭)
- -e:在方法异常之后观察(默认关闭)
- trace——方法内部调用路径,并输出方法路径上的每个节点上耗时
- 常用参数:
- class-pattern:类名表达式匹配
- method-pattern:方法名表达式匹配
- condition-express:条件表达式
- -n:执行次数限制
- #cost:方法执行耗时
- 常用参数:
- stack——输出当前方法被调用的调用路径
- tt——方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
【5】其他
- 使用>将结果重写到日志文件,使用&指令命令是后台运行,session断开不影响任务执行(生命周期默认为1天)
- jobs:列出所有jobkill:强制终止任务
- fg:将暂停的任务拉到前台执行
- bg:将暂停的任务放到后台执行
- grep:搜索满足条件的结果
- plaintext:将命令的结果去除ANSI颜色
- wc:按行统计输出结果
- options:查看或设置Arthas全局开关
- options:https://arthas.aliyun.com/doc/options.html
- profiler/火焰图
- profiler:使用async-profiler对应用采样,生成火焰图
- profiler:https://arthas.aliyun.com/doc/profiler.html
第9章、Java Misssion Control(jmc)
1、历史
- 在Oracle收购Sun之前,Oracle的JRockit虚拟机提供了一款叫做 JRockit Mission Control 的虚拟机诊断工具
- 在Oracle收购sun之后,Oracle公司同时拥有了Hotspot和 JRockit 两款虚拟机。根据Oracle对于Java的战略,在今后的发展中,会将JRokit的优秀特性移植到Hotspot上。其中一个重要的改进就是在Sun的JDK中加入了JRockit的支持
- 在Oracle JDK 7u40之后,Mission Control这款工具己经绑定在Oracle JDK中发布
- 自Java11开始,JFR己经开源。但在之前的Java版本,JFR属于Commercial Feature通过Java虚拟机参数-XX:+UnlockCommercialFeatures 开启
- https://github.com/JDKMissionControl/jmc
2、概述与启动
- Java Misssion Control:位于%JAVA_HOME%/bin/jmc.exe,打开这款软件
- Java Mission Control(简称JMC),Java官方提供的性能强劲的工具 是一个用于对Java应用程序进行管理、监视、概要分析和故障排除的工具套件
- 它包含一个 GUI客户端,以及众多用来收集Java 虚拟机性能数据的插件,如JMX Console(能够访问用来存放虚拟机各个子系统运行数据的MXBeans),以及虚拟机内置的高效profiling工具 Java Flight Recorder ( JFR)
- JMC的另一个优点就是:采用取样,而不是传统的代码植入技术,对应用性能的影响非常非常小,完全可以开着JMC来做压测(唯一影响可能是 full gc多了)
3、功能:实时监控JVM运行时的状态
-
远程服务器,使用前开JMX
-Dcom.sun.management.jmxremote.port=${YOUR PORT} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ss1=false -Djava.rmi.server.hostname=${YOUR HOST/IP}
- 文件 —> 连接 —> 创建新连接,填入上面JMX参数的host 和port
4、Java Flight Recorder
【1】简介
-
Java Flight Recorder,java飞行记录仪,Java飞行记录器
-
Java Flight Recorder是 JMC的其中一个组件
-
Java Flight Recorder能够以极低的性能开销收集Java 虚拟机的性能数据
-
JFR的性能开销很小,在默认配置下平均低于1%。与其他工具相比,JFR能够直接访问虚拟机内的数据,并且不会影响虚拟机的优化。因此,它非常适用于生产环境下满负荷运行的Java程序
-
Java Flight Recorder和DK Mission Control共同创建了一个完整的工具链
-
JDK Mission Control可对Java Flight Recorder连续收集低水平和详细的运行时信息进行高效,详细的分析
【2】事件类型
-
当启用时 JFR将记录运行过程中发生的一系列事件。其中包括Java层面的事件如线程事件、锁事件,以及Java虚拟机内部的事件,如新建对象,垃圾回收和即时编译事件。按照发生时机以及持续时间来划分,JFR的事件共有四种类型,它们分别为以下四种:
-
1、瞬时事件(Instant Event) :用户关心的是它们发生与否,例如异常、线程启动事件
-
2、持续事件(Duration Event) :用户关心的是它们的持续时间,例如垃圾回收事件
-
3、计时事件(Timed Event) :是时长超出指定阈值的持续事件
-
4、取样事件(Sample Event):是周期性取样的事件
-
-
取样事件的其中一个常见例子便是方法抽样(Method Sampling),即每隔一段时问统计各个线程的栈轨迹。如果在这些抽样取得的栈轨迹中存在一个反复出现的方法,那么我们可以推测该方法是热点方法
【3】启动方式
【3.1】方式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:StartFlightRecording=maxage=10m, maxsize=100m, name=SomeLabel MyApp
- 比如:java:
【3.2】方式2:使用jcmd的JFR.*子命令
- 通过jcmd来让JFR开始收集数据、停止收集数据,或者保存所收集的数据
- 对应的子命令分别为JFR.start,JFR.stop,以及JFR.dump
$ jcmd <PID> JFR.start settings=profile maxage=10m maxsize=150m name=SomeLabel
- 上述命令运行过后,目标进程中的JFR已经开始收集数据。此时,可以通过下述命令来导出已经收集到的数据:
$ jcmd <PID> JFR.dump name=SomeLabel filename=myrecording.jfr
- 最后,我们可以通过下述命令关闭目标进程中的 JFR:
$ jcmd <PID> JFR.stop name=SomeLabel
【3.3】方式3:JMC的JFR插件
-
具体使用:
-
1、启动飞行记录仪
-
2、选择事件选项
-
3、 正式启动
【4】Java Flight Recorder 取样分析
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
*/
public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> list = new ArrayList<>();
while(true){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(100 * 50)));
}
}
}
class Picture{
private byte[] pixels;
public Picture(int length) {
this.pixels = new byte[length];
}
public byte[] getPixels() {
return pixels;
}
public void setPixels(byte[] pixels) {
this.pixels = pixels;
}
}
-
1、一般信息
-
2、内存
-
3、代码
-
4、线程
-
5、I/O
-
6、系统
-
7、事件
第10章、其他工具
1、Flame Graphs(火焰图)
- 在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直观的展示CPU在程序整个生命周期过程中时间分配的工具。火焰图对于现代的程序员不应该陌生,这个工具可以非常直观的显示出调用找中的CPU消耗瓶颈
- 网上的关于Java火焰图的讲解大部分来自于Brenden Gregg的博客
- 火焰图,简单通过x轴横条宽度来度量时间指标,y轴代表线程栈的层次
2、Tprofiler
-
案例: 使用JDK自身提供的工具进行JVM调优可以将下 TPS 由2.5提升到20(提升了7倍),并准确定位系统瓶颈
-
系统瓶颈有:应用里释态对象不是太多、有大量的业务线程在频繁创建一些生命周期很长的临时对象,代码里有问题
-
那么,如何在海量业务代码里边准确定位这些性能代码?这里使用阿里开源工具 Tprofiler 来定位 这些性能代码,成功解决掉了GC 过于频繁的性能瓶预,并最终在上次优化的基础上将 TPS 再提升了4倍,即提升到100
-
Tprofiler配置部署、远程操作、 日志阅谈都不太复杂,操作还是很简单的。但是其却是能够 起到一针见血、立竿见影的效果,帮我们解决了GC过于频繁的性能瓶预
-
Tprofiler最重要的特性就是能够统汁出你指定时间段内 JVM 的 top method,这些 top method 极有可能就是造成你 JVM 性能瓶颈的元凶。这是其他大多数 JVM 调优工具所不具备的,包括 JRockit Mission Control。JRokit 首席开发者 Marcus Hirt 在其私人博客《 Lom Overhead Method Profiling cith Java Mission Control》下的评论中曾明确指出 JRMC 井不支持 TOP 方法的统计
-
-
官方地址:http://github.com/alibaba/TProfiler
3、Btrace
- 常见的动态追踪工具有BTrace、HouseHD(该项目己经停止开发)、Greys-Anatomy(国人开发个人开发者)、Byteman(JBoss出品),注意Java运行时追踪工具井不限干这几种,但是这几个是相对比较常用的
- BTrace是SUN Kenai 云计算开发平台下的一个开源项目,旨在为java提供安全可靠的动态跟踪分析工具。先看一下BTrace的官方定义:
- 大意是一个 Java 平台的安全的动态追踪工具,可以用来动态地追踪一个运行的 Java 程序。BTrace动态调整目标应用程序的类以注入跟踪代码(字节码跟踪)