使用上一章命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们存在下列局限:
1)无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间
等(这对定位应用性能瓶颈至关重要)。
2)要求用户登录到目标Java应用所在的宿主机上,使用起来不是很方便。
3)分析数据通过终端输出,结果展示不够直观。
为此,JDK提供了一些内存泄漏的分析工具,如jconsole,jvisualvm等,用于辅助开发人员定位问题,但是这些工具很多时候并不足以满足快速定位的需求。所以这里我们介绍的工具相对多一些、丰富一些。
图形化综合诊断工具年
JDK自带的工具
jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等
位置: jdk lbinljconsole.exe
Visual VM:Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。
位置:jdk \binljvisualvm.exe
JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。
第三方工具
MAT: MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。Eclipse的插件形式
Profiler:商业软件,需要付费。功能强大。与 VisualVM类似
Arthas:Alibaba开源的Java诊断工具。深受开发者喜爱。
Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息。
从Java5开始,在JDK中自带的java监控和管理控制台。
用于对VM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。
官方教程:
基本概述(重点掌握)
Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具。
它集成了多个JDK命令行工具,使用visual VM可用于显示虚拟机进程及进程的配置和环境信息
(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。
在DK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM 在JDK/bin目录下),即:它完全免费。|
此外,Visual VM也可以作为独立的软件安装:
首页
在jdk安装目录中找到jvisualvm.exe,然后双击执行即可。或打开DOS窗口,输入jvisualvm就可以打开该软件。
单独安装叫Visual VM,jdk自带叫jvisualvm。
安装插件
Visual VM的一大特点是支持插件扩展,并且插件安装非常方便。我们既可以通过离线下载插件文件*.nbm,然后在Plugin对话框的已下载页面下,添加已下载的插件。也可以在可用插件页面下,在线安装插件。(这里建议安装上:VisualGC)
使用本地连接,在idea启动OOMTest.java,在DOS窗口输入jvisualvm,然后选择OOMTest,进入监控界面。
导出heapdump、threaddump
先点击右侧堆dump,然后点击左侧的heapdump文件,将其保存到桌面。
线程dump
先点击线程dump,然后点击左侧的threaddump,保存到桌面。
导入heapdump、threaddump
点击文件,选择装入,找到文件位置,选择要导入的文件类型
导入heapdump
分析过程:十分重要
点击查找,找到最大的对象
点击#84进入,发现ArrayList里面存储的就是#1856
选中ArrayList,点击在线程中显示。
直接定位到具体的类的方法:at com.atguigu.springcloud.jvm.OOMTest.main(OOMTest.java:16)
如此便定位到了堆中最大的对象的位置。然后就可以去翻看代码,查看代码逻辑是否有问题。
配合下面的步骤,具体定位到原因。
点击#1856
点击#569,发现Object数组里面存储的是Picture
点击#887,发现Picture里面有byte[ ]
由此,我们遍发现了是OOMTest类中的main方法中的list对象中存储的Picture对象太多导致的内存占用过高。
导入threaddump
文件–>装入–>修改文件类型
jvisualvm分析线程dump好像不怎么方便的样子,暂且点到为止。
MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。
MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。
下载:
只要确保机器上装有JDK并配置好相关的环境变量,MAT可正常启动。还可以在Eclipse中以插件的方式安装:
dump文件内容
MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。
一般说来,这些内存信息包含:
所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。
所有的类信息,包括classloader、类名称、父类、静态变量等.
GCRoot到所有的这些对象的引用路径
线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)
说明1:
MAT不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如Sun,HP,SAP所采用的 HPROF 二进制堆存储文件,以及IBM的 PHD堆存储文件等都能被很好的解析。
说明2:
最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现。
获取dump文件
方法一:通过前一章介绍的 jmap工具生成,可以生成任意一个java进程的dump文件
方法二:通过配置JVM参数生成。
选项"-XX:+HeapDumpOnoutOfMemoryError”或"-XX:+HeapDumpBeforeFullGC"
选项"-XX :HeapDumpPath"所代表的含义就是当程序出现OutOfMemory时,将会在相应的目录下生成一份dump文件。如果不指定选项“-XX:HeapDumpPath”则在当前目录下生成dump文件。
对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用jmap+MAT工具是最常见的组合。
方法三:使用VisualVM可以导出堆dump文件
方法四:使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动Java程序中导出堆快照。该功能将借助jps列出当前正在运行的Java 进程,以供选择并获取快照。
打开堆dump文件
如果是第一次打开,就选第一个,否则,选最后一个。
MAT菜单栏
Histogram(直方图)
寻找指定对象的GC ROOTS(只看强引用)
也可以直接搜索
查看所有的线程
下图中的local指的就是局部变量
获得对象互相引用的关系
Leak Suspects(内存泄露分析)
想要看详情可以点击上图的Details>>
浅堆与深堆
浅堆(Shallow Heap)是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同,对象的大小可能会向8字节进行对齐。
以String为例: 2个int值共占8字节,对象引用占用4字节,对象头8字节,合计20字节,
故占24字节。(jdk7中)
int hash32 0
int hash 0
ref value C:Users\Administrat
这24字节为String对象的浅堆大小。它与String的value实际取值无关,无论字符串长度如何,浅堆大小始终是24字节。
保留集(Retained Set):
对象A的保留集指当对象A被垃圾回收后,可以被释放的所有的对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象A所持有的对象的集合。
深堆(Retained Heap):
深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。
支配树(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。
注意:
从“对象引用图“到”支配树”
支配者:如果要到达对象B,必须经过对象A,那么对象A就是对象B的支配者,可以想到支配者大于等于1。
直接支配者:在支配者中距离对象B最近的对象A就是对象B的直接支配者,直接支配者不一定就是对象B的上一级,直接支配者只有一个。
支配树是怎么画?支配树中的对象与对象之间的关系就是直接支配关系,也就是上一级是下一级的直接支配者,只要按照这样的方式来作图,肯定从“对象引用图“到”支配树”。
内存泄露
内存泄漏与内存溢出的关系:
内存泄漏( memory leak )
申请了内存用完了不释放,比如一共有1024M 的内存,分配了512M的内存一直不回收,那么可以用的内存只有512M了,仿佛泄露掉了一部分;通俗一点讲的话,内存泄漏就是【占着茅坑不拉shil. lI
内存溢出(out of memory)
申请内存时,没有足够的内存可以使用;
通俗一点儿讲,一个厕所就三个坑,有两个占着茅坑不走的(内存泄漏)﹐剩下最后一个坑,厕所表示接待压力很大,这时候一下子来了两个人,坑位(内存)就不够了,内存泄漏变成内存溢出了。
可见,内存泄漏和内存溢出的关系:内存泄漏的增多,最终会导致内存溢出。量变引起质变
泄漏的分类
经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存;
偶然发生:在某些特定情况下才会发生
一次性:发生内存泄露的方法只会执行一次;
隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。
Java中内存泄露的8种情况
1)静态集合类
静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
2)单例模式
单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM的生命周期一样长,所以如果单例对象如果持有外部对象的引用。那么这个外部对象也不会被回收,那么就会造成内存泄漏。
3)内部类持有外部类
内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象。这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。
4)各种连接,如数据库连接、网络连接和IO连接等
各种连接,如数据库连接、网络连接和IO连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
5)变量不合理的作用域
6)改变哈希值
改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。|
否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏。
这也是string为什么被设置成了不可变类型,我们可以放心地把 string存入 HashSet,或者把String 当做HashMap 的key值;
当我们想把自己定义的类保存到散列表的时候,需要保证对象的 hashCode不可变。
/**
* 演示内存泄漏
*
* @author shkstart
* @create 14:43
*/
public class ChangeHashCode {
public static void main(String[] args) {
HashSet set = new HashSet();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
p1.name = "CC";//导致了内存的泄漏
set.remove(p1); //删除失败
System.out.println(set);
set.add(new Person(1001, "CC"));
System.out.println(set);
set.add(new Person(1001, "AA"));
System.out.println(set);
}
}
class Person {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
if (id != person.id) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
7)缓存泄露
内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。
对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。
/**
* 演示内存泄漏
*
* @author shkstart
* @create 14:53
*/
public class MapTest {
static Map wMap = new WeakHashMap();
static Map map = new HashMap();
public static void main(String[] args) {
init();
testWeakHashMap();
testHashMap();
}
public static void init() {
String ref1 = new String("obejct1");
String ref2 = new String("obejct2");
String ref3 = new String("obejct3");
String ref4 = new String("obejct4");
wMap.put(ref1, "cacheObject1");
wMap.put(ref2, "cacheObject2");
map.put(ref3, "cacheObject3");
map.put(ref4, "cacheObject4");
System.out.println("String引用ref1,ref2,ref3,ref4 消失");
}
public static void testWeakHashMap() {
System.out.println("WeakHashMap GC之前");
for (Object o : wMap.entrySet()) {
System.out.println(o);
}
try {
System.gc();
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("WeakHashMap GC之后");
for (Object o : wMap.entrySet()) {
System.out.println(o);
}
}
public static void testHashMap() {
System.out.println("HashMap GC之前");
for (Object o : map.entrySet()) {
System.out.println(o);
}
try {
System.gc();
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("HashMap GC之后");
for (Object o : map.entrySet()) {
System.out.println(o);
}
}
}
/**
* 结果
* String引用ref1,ref2,ref3,ref4 消失
* WeakHashMap GC之前
* obejct2=cacheObject2
* obejct1=cacheObject1
* WeakHashMap GC之后
* HashMap GC之前
* obejct4=cacheObject4
* obejct3=cacheObject3
* Disconnected from the target VM, address: '127.0.0.1:51628', transport: 'socket'
* HashMap GC之后
* obejct4=cacheObject4
* obejct3=cacheObject3
**/
实际工作中并不建议使用WeakHashMap,可以使用HashMap,限制HashMap的数量,以及不使用了及时清空即可。
8)监听器和回调
内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚。
需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为weakHashMap护的键。
内存泄露案例分析
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) { //入栈
ensureCapacity();
elements[size++] = e;
}
//存在内存泄漏
// public Object pop() { //出栈
// if (size == 0)
// throw new EmptyStackException();
// return elements[--size];
// }
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
上述程序并没有明显的错误,但是这段程序有一个内存泄漏,随着GC活动的增加,或者内存占用的不断增加,程序性能的降低就会表现出来,严重时可导致内存泄漏,但是这种失败情况相对较少。代码的主要问题在pop函数。
当进行大量的pop操作时,由于引用未进行置空,gc是不会释放的。
如果栈先增长,然后收缩,那么从栈中弹出的对象将不会被当作垃圾回收,即使程序不再使用栈中的这些队象,他们也不会回收,因为栈中仍然保存这对象的引用,俗称过期引用,这个内存泄露很隐蔽。
所以当对象出栈后及时将引用置空。elements[size] = null;防止内存泄露。
支持使用OQL语言查询对象信息
SELECT子句:
FROM子句:
WHERE子句
内置对象与方法
在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在
eclipse里面有 Eclipse Memory Analyzer too1(MAT)插件可以测试,而在IDEA中也有这么一个插件,就是JProfiler。
JProfiler是由 ej-technologies公司开发的一款 Java应用性能诊断工具。功能强大,但是收费。
官网下载地址:
特点:
使用方便、界面操作友好(简单且强大)·对被分析的应用影响小(提供模板).
CPU,Thread ,Memory分析功能尤其强大
支持对jdbc , noSql,jsp, servlet,socket等进行分析
支持多种模式(离线,在线)的分析
支持监控本地、远程的JVM
跨平台,拥有多种操作系统的安装版本
下载与安装
使用特别版直接安装,然后使用注册码激活。
在idea中直接安装插件即可,如果下载不下来,当然也可以使用jar包安装插件。我的直接下载成功了,然后重启idea。
11版本的JProfiler插件直接链接了idea,不用再手动关联了。
JProfier数据采集方式分为两种:Sampling(样本采集)和Instrumentation(重构模式)
Instrumentation:这是Profiler全功能模式。在class加载之前,JProfier把相关功能代码写入到需要分析的class的bytecode中,对正在运行的jvm有一定影响。
优点:功能强大。在此设置中,调用堆栈信息是准确的。
缺点:若要分析的class较多,则对应用的性能影响较大,CPU开销可能很高(取决于Filter的控制)。因此使用此模式一般配合Filter使用,只对特定的类或包进行分析
Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来。
优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)·缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)
注: JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是Profiler的数据采集类型。
1)、分析对象的引用
从all Objects跳转到heap walker
使用选中的对象
看看谁引用了这个对象
找到最终引用的位置
Shallow size、 Retained size、 Deep size
Shallow(浅) size:就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。
Retained(保留) size:是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。
Deep(深) size:包含那些对象的大小。深大小与保留大小的区别在于深大小包含那些存在共享的对象,但是保留大小则不包括。
在图表中展示
2)、导出hprof文件
代码
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";
// ArrayList list = new ArrayList();
static ArrayList list = new ArrayList();
}
使用profiler启动程序
发现内存直线上升
然后在所有对象中标记一下,看看谁增长最快且不能被回收
然后在heap walker中分析
接着在heap walker中选中该对象,并分析是谁在引用当前对象
定位到是谁在引用
翻看代码定位到MemoryLeak和Bean类,发现Bean类中定义了一个静态的list,该list在gc过程中无法被回收。可以考虑将其改为非静态或者在执行完成后将该list清空。 Bean.list.clear();
代码修改后内存回收如下图,说明list可以被回收掉了,内存上去了又下来了,不会一直上升。
这两款工具在业界知名度也比较高,他们的优点是可以图形界面上看到各维度的性能数据,使用者根据这些数据进行综合分析,然后判断哪里出现了性能问题。
但是这两款工具也有个缺点,都必须在服务端项目进程中配置相关的监控参数。然后工具通过远程连接到项目进程,获取相关的数据。这样就会带来一些不便,比如线上环境的网络是隔离的,本地的监控工具根本连不上线上环境。并且类似于Jprofiler这样的商业工具,是需要付费的。
那么有没有一款工具不需要远程连接,也不需要配置监控参数,同时也提供了丰富的性能监控数据呢?
今天跟大家介绍一款阿里巴巴开源的性能分析神器Arthas(阿尔萨斯)
Arthas(阿尔萨斯)是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控VM状态。
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
这个类从哪个jar包加载的?为什么会报各种类相关的Exception?
我改的代码为什么没有执行到?难道是我没commit?分支搞错了?
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
是否有一个全局视角来查看系统的运行状况?
有什么办法可以监控到JVM的实时运行状态?怎么快速定位应用的热点,生成火焰图?
基于哪些工具开发而来
greys-anatomy: Arthas代码基于Greys二次开发而来,非常感谢Greys之前所有的工作,以及Greys原作者对Arthas提出的意见和建议!
termd: Arthas的命令行实现基于termd开发,是一款优秀的命令行程序开发框架,感谢termd提供了优秀的框架。
crash: Arthas的文本渲染功能基于crash中的文本渲染功能开发,可以从这里看到源码,感谢crash在这方面所做的优秀工作。
cli: Arthas的命令行界面基于vert.x提供的cli库进行开发,感谢vert.x在这方面做的优秀工作。
compiler Arthas里的内存编绎器代码来源
Apache Commons Net Arthas里的Telnet Client代码来源
JavaAgent:运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行
premain方法然后再执行main方法
ASM:一个通用的Java字节码操作和分析框架。它可以用于修改现有的类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从它们构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的功能,但是主要关注性能。因为它被设计和实现得尽可能小和快,所以非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)
官方文档
安装方式一:可以直接在Linux上通过命令下载
可以在官方Github 上进行下载,如果速度较慢,可以尝试国内的码云 Gitee下载。
github下载
Gitee下载
卸载:
在 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 fromhttps://github.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:
java -jar arthas-boot.jar
选择进程(输入[]内编号(不是PID)回车)
[INFO] arthas-boot version: 3.1.4
[INFO] Found existing java process,please choose one and hit RETURN.
*[1]:11616 com.ArthasI
[2]:8676
[3]:16200 org.jetbrains.jps.cmdline.Launcher
[4]: 21032 org.jetbrains.idea.maven.server.RemoteMavenServer
方式2:运行时选择Java进程 PID
java -jar arthas-boot.jar [PID]
退出
最后一行[arthas@7457]$,说明打开进入了监控客户端,在这里就可以执行相关命令进行查看了。
使用quit/exit:退出当前客户端
使用stop/shutdown:关闭arthas服务端,并退出所有客户端。
查看日志:cat ~/logs/arthas/arthas.log
查看帮助:java -jar arthas-boot.jar -h
dashboard -i 3000 -n 2:i是时间间隔,ms ; n是打印次数
thread
heapdump --live /home/hello/1.hprof
sc com.atguigu.*
sc -d -f com.atguigu.jvm.controller.TestController
sm
sm com.atguigu.jvm.controller.TestController:没带方法就会列出所有的方法
sm -d com.atguigu.jvm.controller.TestController test:带上方法名和-d会显示该方法的详细信息
jad com.atguigu.jvm.controller.TestController test 反编译已加载的指定类
trace指令十分常用,比如某个接口响应迟钝,排查一下性能瓶颈在哪?
启动arthas
java -jar arthas-boot.jar
输入想要监控的服务对应的中括号中的数字
trace com.test.study.TestController test
耗时最长的方法会被标红,然后重点分析该方法的代码,进行优化
诊断结束,执行stop命令,会还原所有被增强过的类。
profiler start:启动 profiler
profiler getSamples:获取已采集的 sample 的数量
profiler status:查看 profiler 状态
profiler stop --file /home/hello/1.html:停止 profiler,默认情况下,结果文件是html格式,也可以用–format参数指定。或在–file参数里用文件名指名格式。
sz 1.html:下载1.html并保存到桌面
使用浏览器打开1.html
参考博客
火焰图,简单通过x轴横条宽度来度量时间指标,y轴代表线程栈的层次。
Java Flight Recorder
方式1:使用-XX:StartFlightRecording=参数
方式2:使用jcmd的JFR.*子命令
方式3:JMC的JFR插件
/**
* -Xms600m -Xmx600m -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
* @author shkstart [email protected]
* @create 2020 21:12
*/
public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> list = new ArrayList<>();
while(true){
try {
Thread.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(1024 * 1024)));
}
}
}
class Picture{
private byte[] pixels;
public Picture(int length) {
this.pixels = new byte[length];
}
}
注:本文是学习 尚硅谷宋红康JVM全套教程(详解java虚拟机)所做笔记。