目录

  • 工具概述
    • 图形化综合诊断工具
  • jConsole
    • 基本概述
    • 启动
    • 三种连接方式
  • Visual VM
    • 基本概述
    • 插件的安装
    • 连接方式
    • 基本使用
  • eclipse MAT
    • 基本概述
    • 获取堆dump文件
      • dump文件内容
      • 两点说明
      • 获取dump文件
    • 分析堆dump文件
      • histogram
      • thread overview和获取对象间相互引用的关系
      • 深堆与浅堆
        • shallow heap
        • Retained Heap
        • 对象的实际大小
        • StudentTrace案例分析
      • 支配树(Dominator Tree)
    • 案例:Tomcat堆溢出分析
      • 说明
      • 分析过程
  • 补充一:再谈内存泄漏
    • 内存泄漏的理解与分类
    • Java中内存泄漏的8中情况
      • 静态集合类
      • 单例模式
      • 内部类持有外部类
      • 各种连接,如数据库连接、网络连接和IO连接等
      • 变量不合理的作用域
      • 改变哈希值
      • 缓存泄漏
      • 监听器和回调
    • 内存泄漏案例分析
      • 案例1
      • 案例2
  • 补充二:支持使用OQL语言查询对象信息
    • SELECT子句
    • From子句
    • where子句
    • 内置对象与方法
  • JProfiler
    • 基本概述
    • 安装与配置
    • 具体使用
      • 数据采集方式
      • 遥感检测Telemetries
      • 内存视图Live memory
      • 堆遍历heap walker
      • CPU视图CPU views
      • 线程视图threads
      • 监视器和锁Monitors & locks
    • 案例分析
      • 案例1
      • 案例2
  • Arthas
    • 基本概述
    • 安装与使用
    • 相关诊断指令
  • Java Mission Control
    • 历史
    • 启动
    • 概述
    • 功能:实时监控JVM运行时的状态
    • Java Flight Recorder
      • 事件类型
      • 启动方式
    • Java Flight Recorder取样分析
  • 其他工具
    • Flame Graphs(火焰图)
    • Tprofiler
    • Btrace
    • Yourkit and Jprobe and Spring Insight
  • 结语

工具概述

使用上一章命令行工具或组合能帮您获取目标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:商业软件,需要付费。功能强大。与 VisualVM类似
    • Arthas:Alibaba开源的Java诊断工具。深受开发者喜爱。
    • Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息。

jConsole

基本概述

  • 从Java5开始,在JDK中自带的java监控和管理控制台。
  • ·用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。

官方教程点这里

启动

方式一:
直接去JDK的bin目录下找到jConsole程序,点击启动即可

方式二:
在已经配置过环境变量的前提下,直接在命令行输入jConsole即可启动。

三种连接方式

1.Local:使用JConsole连接一个正在本地系统运行的JVM,并且执行程序的和运行JConsole的需要是同一个用户。JConsole使用文件系统的授权通过RMI连接器连接到平台的MBean服务器上。这种从本地连接的监控能力只有Sun的JDK具有。

测试代码:

package priv.user.jvm;import java.util.ArrayList;/*** Description:* Author:江洋大盗* Date:2021/3/15 21:53* -Xms600m -Xmx600m -XX:SurvivorRatio=8*/
public class JMAPTest {public static void main(String[] args) {ArrayList<byte[]> list = new ArrayList<>();for (int i = 0; i < 1000; i++) {byte[] bytes = new byte[1024*100];//100kblist.add(bytes);try {Thread.sleep(1500);} catch (InterruptedException e) {e.printStackTrace();}}}
}


2.Remote:使用下面的URL通过RMI连接器连接到一个JMX代理,service:jmx:rmi:///jindi/rmi://hostName.portNum/jmxrmi。JConsole为建立连接,需要在环境变量中设置mx.remote.credentials来指定用户名和密码,从而进行授权。

3.Advanced:使用一个特殊的URL连接JMX代理。一般情况使用自己定制的连接器而不是RMI提供的连接器来连接JMX代理,或者是一个使用JDK1.4的实现了JMX和JMX Rmote的应用。

Visual VM

基本概述

  • Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具。
  • 它集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。
  • ·在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM 在JDK/bin目录下),即:它完全免费。
  • ·此外,Visual VM也可以作为独立的软件安装

首页直达

插件的安装

Visual VM的一大特点是支持插件扩展,并且插件安装非常方便。我们既可以通过离线下载插件文件*.nbm,然后在Plugin对话框的已下载页面下,添加己下载的插件。也可以在可用插件页面下,在线安装插件。(这里建议安装上: VisualGC)

插件下载

也可以在客户端中直接安装插件

Visual VM可以作为插件安装在idea中。

连接方式

1.本地连接

  • 监控本地Java进程的CPU、类、线程等

2.远程连接:

  • 确定远程服务器的ip地址
  • 添加JMX(通过JMX技术具体监控远端服务器哪个Java进程
  • 修改bin/catalina.sh文件,连接远程的tomcat
  • 在…/conf中添加jmxremote.access和jmxremote.password文件
  • 将服务器地址改为公网ip地址
  • 设置阿里云安全策略和防火墙策略
  • 启动tomcat,查看tomcat启动日志和端口监听
  • JMX中输入端口号、用户名、密码登录

基本使用

点此查看基本宋红康老师讲解的Visual VM的基本使用

eclipse MAT

基本概述

MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。

MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。大家可以点击这里去下载并使用MAT。

获取堆dump文件

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文件

histogram

MAT的直方图和jmap的-histo子命令一样,都能够展示各个类的实例数目以及这些实例的 Shallowheap总和。但是,MAT的直方图还能够计算 Retained heap,并支持基于实例数目或 Retainedheap 的排序方式(默认为Shallow heap)。

此外,MAT还可以将直方图中的类按照超类、类加载器或者包名分组。

当选中某个类时,MAT界面左上角的 Inspector窗口将展示该类的class 实例的相关信息,如类加载器等。

点击这里查看宋红康老师讲解的histogram功能概述

thread overview和获取对象间相互引用的关系

查看系统中的Java线程、查看局部变量的信息。

点击这里查看宋红康老师讲解的thread overview功能概述

深堆与浅堆

shallow heap

浅堆(Shallow Heap)是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同,对象的大小可能会向8字节进行对齐。I

以String为例:2个int值共占8字节,对象引用占用4字节,对象头8字节,合计20字节,向8字节对齐,故占24字节。(jdk7中)

int hash32 0
int hash 0
ref value C:\Users\Administrat

这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的深堆范围内。

StudentTrace案例分析

点击这里查看宋红康老师讲解的案例

支配树(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中,单击工具栏上的对象支配树按钮,可以打开对象支配树视图。

案例:Tomcat堆溢出分析

说明

点击这里查看宋红康老师的案例讲解

Tomcat是最常用的Java Servlet容器之一,同时也可以当做单独的Web服务器使用。Tomcat本身使用Java实现,并运行于Java虚拟机之上。在大规模请求时,Tomcat有可能会因为无法承受压力而发生内存溢出错误。这里根据一个被压垮的Tomcat的堆快照文件,来分析Tomcat在崩溃时的内部情况。

分析过程











补充一:再谈内存泄漏

内存泄漏的理解与分类

何为内存泄漏?(memory leak)

可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。
是否还被使用? 是
是否还被需要? 否

内存泄漏的理解
严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。

但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”

对象X引用对象 Y,X的生命周期比Y的生命周期长;
那么当Y生命周期结束的时候,X依然引用着Y,这时候,垃圾回收期是不会回收对象Y的;
如果对象X还引用着生命周期比较短的A、B、C,对象A又引用着对象 a、b、c,这样就可能造成大量无用的对象不能被回收,进而占据了内存资源,造成内存泄漏,直到内存溢出。

内存泄漏与内存溢出的关系
1.内存泄漏(memory leak )
申请了内存用完了不释放,比如一共有1024M 的内存,分配了512M 的内存一直不回收,那么可以用的内存只有512M了,仿佛泄露掉了一部分;
通俗一点讲的话,内存泄漏就是【占着茅坑不拉shi】。

2.内存溢出(out of memory)
申请内存时,没有足够的内存可以使用;
通俗一点儿讲,一个厕所就三个坑,有两个站着茅坑不走的(内存泄漏),剩下最后一个坑,厕所表示接待压力很大,这时候一下子来了两个人,坑位(内存)就不够了,内存泄漏变成内存溢出了。

可见,内存泄漏和内存溢出的关系:内存泄漏的增多,最终会导致内存溢出。

泄漏的发生

  • 经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存;
  • 偶然发生:在某些特定情况下才会发生;
  • 一次性:发生内存泄露的方法只会执行一次;
  • 隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。

Java中内存泄漏的8中情况

静态集合类

静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。

代码示例

public class StaticList {static List list = new ArrayList<>();public void oomTest() {Object obj = new Object();//局部变量list.add(obj);}
}

单例模式

单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM 的生命周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。

内部类持有外部类

内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象。
这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。

各种连接,如数据库连接、网络连接和IO连接等

在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。
否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。

代码示例

    public void conTest() {try {Connection conn = null;Class.forName("com.mysql.jdbc.Driver");conn = DriverManager.getConnection("url", "", "");Statement statement = conn.createStatement();ResultSet res = statement.executeQuery("...");} catch (ClassNotFoundException | SQLException e) {//异常日志e.printStackTrace();} finally {//1.关闭结果集 Statement//2.关闭声明的对象 ResultSet//3.关闭连接 Connection}}

变量不合理的作用域

变量不合理的作用域。一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。

代码示例

public class UsingRandom{private String msg;public void receiveMsg(){readFromNet();//从网络中接受数据保存到msg中saveDB();//把msg保存到数据库中}
}

如上面这个伪代码,通过readFromNet方法把接受的消息保存在变量msg中,然后调用saveDB方法把msg的内容保存到数据库中,此时msg已经就没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄漏。

实际上这个msg变量可以放在receiveMsg方法内部,当方法使用完,那么msg的生命周期也就结束,此时就可以回收了。还有一种方法,在使用完msg后,把msg设置为null,这样垃圾回收器也会回收msg的内存空间。

改变哈希值

改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。

否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏。

这也是 String为什么被设置成了不可变类型,我们可以放心地把 String存入 HashSet,或者把String当做 HashMap的key 值;

当我们想把自己定义的类保存到散列表的时候,需要保证对象的 hashCode不可变。

代码示例1:

package priv.jvm;import java.util.HashSet;/*** Description:* Author:江洋大盗* Date:2021/3/21 17:07*/
public class ChangeHashCode1 {public static void main(String[] args) {HashSet<Point> points = new HashSet<>();Point point = new Point();point.setX(10);//HashCode=10points.add(point);point.setX(20);//HashCode=20 此行为导致内存泄漏System.out.println("points.remove = " + points.remove(point));//falsepoints.add(point);System.out.println("points.size = " + points.size());//size = 2System.out.println(points);}
}class Point {int x;public int getX() {return x;}public void setX(int x) {this.x = x;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (!(o instanceof Point)) return false;Point point = (Point) o;return getX() == point.getX();}@Overridepublic int hashCode() {return getX();}@Overridepublic String toString() {return "Point{" +"x=" + x +'}';}
}

输出结果:

points.remove = false
points.size = 2
[Point{x=20}, Point{x=20}]

代码示例2:

package priv.jvm;import java.util.HashSet;/*** Description:* Author:江洋大盗* Date:2021/3/21 17:24*/
public class ChangeHashCode2 {public static void main(String[] args) {HashSet<Person> people = new HashSet<>();Person p1 = new Person("AA", 1001);Person p2 = new Person("BB", 1002);people.add(p1);people.add(p2);p1.setName("CC");//导致内存泄漏people.remove(p1);//删除失败System.out.println(people);people.add(new Person("CC", 1001));System.out.println(people);people.add(new Person("AA", 1001));System.out.println(people);}
}class Person {private String name;private int id;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}public Person(String name, int id) {this.name = name;this.id = id;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (!(o instanceof Person)) return false;Person person = (Person) o;if (getId() != person.getId()) return false;return getName() != null ? getName().equals(person.getName()) : person.getName() == null;}@Overridepublic int hashCode() {int result = getName() != null ? getName().hashCode() : 0;result = 31 * result + getId();return result;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", id=" + id +'}';}
}

输出结果:

[Person{name='CC', id=1001}, Person{name='BB', id=1002}]
[Person{name='CC', id=1001}, Person{name='CC', id=1001}, Person{name='BB', id=1002}]
[Person{name='CC', id=1001}, Person{name='CC', id=1001}, Person{name='AA', id=1001}, Person{name='BB', id=1002}]

内存结构分析:

缓存泄漏

内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。

对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。

代码示例:

package priv.jvm;import com.alibaba.druid.support.spring.stat.SpringStatUtils;import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;/*** Description:* Author:江洋大盗* Date:2021/3/21 17:42*/
public class MapTest {static Map map = new HashMap<>();static Map wmap = new WeakHashMap();public static void main(String[] args) {init();testWeakHashMap();testHashMap();}public static void init() {String ref1 = new String("object1");String ref2 = new String("object2");String ref3 = new String("object3");String ref4 = new String("object4");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);}System.gc();try {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);}System.gc();try {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之前
object2=cacheObject2
object1=cacheObject1
WeakHashMap GC之后
HashMap GC之前
object4=cacheObject4
object3=cacheObject3
HashMap GC之后
object4=cacheObject4
object3=cacheObject3

监听器和回调

内存泄漏另一个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚。
需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为WeakHashMap中的键。
案例中将涉及这部分的例子。

内存泄漏案例分析

案例1

代码:

package priv.memoryleak;import java.util.Arrays;
import java.util.EmptyStackException;/*** Description:* Author:江洋大盗* Date:2021/3/21 20:12*/
public class Stack {private Object[] elements;//栈底层维护的数组private int size = 0;//记录栈中的数据个数private static final int INIT_SIZE = 16;public Stack() {elements = new Object[INIT_SIZE];}/*** 入栈** @param obj 入栈元素*/public void push(Object obj) {ensureCapacity();elements[size++] = obj;}/*** 有问题的出栈操作* @return 返回栈顶元素*/
//    public Object pop(){//        if(size == 0)
//            throw new EmptyStackException();
//        return elements[--size]; //此处可能造成内存泄漏
//    }/*** 修改后的出栈操作** @return 返回栈顶元素*/public Object pop() {if (size == 0)throw new EmptyStackException();Object obj = elements[--size];elements[size] = null;return obj;}private void ensureCapacity() {if (elements.length == size)elements = Arrays.copyOf(elements, 2 * size + 1);}}

分析:
假设这个栈一直增长,增长后如下图所示。

当进行大量的pop操作时,由于引用未进行置空,gc是不会释放的,如下图所示


从上图中看以看出,如果栈先增长,在收缩,那么从栈中弹出的对象将不会被当作垃圾回收,即使程序不再使用栈中的这些队象,他们也不会回收,因为栈中仍然保存这对象的引用,俗称过期引用,这个内存泄露很隐蔽。

案例2

点击这里查看宋红康老师讲解的第二个案例

补充二:支持使用OQL语言查询对象信息

MAT支持一种类似于SQL的查询语言OQL(Object Query Language)。OQL使用类SQL语法,可以在堆中进行对象的查找和筛选。

SELECT子句

在MAT中,Select子句的格式与SQL基本一致,用于指定要显示的列。Select子句中可以使用 “ * ”,查看结果对象的引用实例(相当于outgoing references)。

SELECT * FROM java.util.Vector v

使用“OBJECTS”关键字,可以将返回结果集中的项以对象的形式显示。

SELECT objects v.elementData FROM java.util.Vector v
SELECT OBECTS 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

From子句

From子句用于指定查询范围,它可以指定类名、正则表达式或者对象地址。SELECT * FROM java. lang.String s

下例使用正则表达式,限定搜索范围,输出所有com.atguigu包下所有类的实例

SELECT * FROM "com\.atguigu\..* "

也可以直接使用类的地址进行搜索。使用类的地址的好处是可以区分被不同ClassLoader加载的同一种类型。

select * from 0x37a0b4d

where子句

Where子句用于指定OQL的查询条件。0QL查询将只返回满足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 WHERE v.elementData.@length>15 AND v.@retainedHeapsize>1000

内置对象与方法

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

JProfiler

基本概述

介绍
在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在eclipse里面有Eclipse Memory Analyzer tool(MAT)插件可以测试,而在IDEA中也有这么一个插件,就是JProfiler。

JProfiler是由 ej-technologies 公司开发的一款Java应用性能诊断工具。功能强大,但是收费。

官网下载地址

特点

  • 使用方便、界面操作友好(简单且强大)
  • 对被分析的应用影响小(提供模板)
  • CPU, Thread , Memory分析功能尤其强大
  • 支持对jdbc,noSql, jsp, servlet, socket等进行分析
  • 支持多种模式(离线,在线)的分析
  • 支持监控本地、远程的JVM
  • 跨平台,拥有多种操作系统的安装版本

    主要功能
  • 方法调用:对方法调用的分析可以帮助您了解应用程序正在做什么,并找到提高其性能的方法
  • 内存分配:通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄漏问题,优化内存使用
  • 线程和锁:JProfiler提供多种针对线程和锁的分析视图助您发现多线程问题
  • 高级子系统:许多性能问题都发生在更高的语义级别上。例如,对于JDBC调用,您可能希望找出执行最慢的SQL语句。JProfiler支持对这些子系统进行集成分析

安装与配置

下载与安装

下载地址
根据软件提示安装即可。

配置
配置方法可以参照这篇博客

具体使用

点击这里查看宋红康老师的视频教程

数据采集方式

JProfier数据采集方式分为两种: Sampling(样本采集)和Instrumentation(重构模式)

  • Instrumentation:这是Profiler全功能模式。在class加载之前,JProfier把相关功能代码写入到需要分析的class的bytecode中,对正在运行的jvm有一定影响。

    • 优点:功能强大。在此设置中,调用堆栈信息是准确的。
    • 缺点:若要分析的class较多,则对应用的性能影响较大,CPU开销可能很高(取决于Filter的控制)。因此使用此模式一般配合Filter使用,只对特定的类或包进行分析。
  • Sampling:类似于样本统计,每隔一定时间(5ms)将每个线程栈中方法栈中的信息统计出来。
    • 优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)
    • 缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)

注:JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是JProfiler的数据采集类型。

遥感检测Telemetries

查看JVM的运行信息

  • 整体视图Overview:显示堆内存、cpu、线程以及GC等活动视图
  • 内存 Memory:显示一张关于内存变化的活动时间表。
  • 记录的对象 Recorded objects:显示一张关于活动对象与数组的图表的活动时间表。
  • 记录吞吐量 Record Throughput:显示一段时间累计的JVM生产和释放的活动时间表。
  • 垃圾回收活动GC Activity:显示一张关于垃圾回收活动的活动时间表。
  • 类classes:显示一个与已装载类的图表的活动时间表。
  • 线程Threads:显示一个与动态线程图表的活动时间表
  • CPU负载CPU Load:显示一段时间中CPU的负载图表。

内存视图Live memory

Live memory内存剖析: class/class instance的相关信息。例如对象的个数,大小,对象创建的方法执行栈,对象创建的热点。

  • 所有对象All Objects:显示所有加载的类的列表和在堆上分配的实例数。只有Java 1.5(JVMTI)才会显示此视图。
  • 记录对象 Record Objects:查看特定时间段对象的分配,并记录分配的调用堆栈。
  • 分配访问树 Allocation call Tree:显示一棵请求树或者方法、类、包或对已选择类有带注释的分配信息的J2EE组件。
  • 分配热点Allocation Hot Spots:显示一个列表,包括方法、类、包或分配已选类的32EE组件。你可以标注当前值并且显示差异值。对于每个热点都可以显示它的跟踪记录树。
  • 类追踪器Class Tracker:类跟踪视图可以包含任意数量的图表,显示选定的类和包的实例与时间。

分析:内存中的对象的情况

  • 频繁创建的Java对象:死循环、循环次数过多
  • 存在大的对象:读取文件时,byte[]应该边读边写。==>如果长时间不写出的话,导致byte[]过大
  • 存在内存泄漏

堆遍历heap walker

  • 类Classes:显示所有类和它们的实例,可以右击具体的类"Used Selected Instance"实现进一步跟踪。
  • 分配Allocations:为所有记录对象显示分配树和分配热点。
  • 索引 References:为单个对象和“显示到垃圾回收根目录的路径”提供索引图的显示功能。还能提供合并输入视图和输出视图的功能。
  • 时间Time:显示一个对已记录对象的解决时间的柱状图。
  • 检查Inspections:显示了一个数量的操作,将分析当前对象集在某种条件下的子集,实质是一个筛选的过程。
  • 图表Graph:你需要在references视图和biggest视图手动添加对象到图表,它可以显示对象的传入和传出引用,能方便的找到垃圾收集器根源。

Ps:在工具栏点击"Go To Start"可以使堆内存重新计数,也就是回到初始状态。

CPU视图CPU views

JProfiler提供不同的方法来记录访问树以优化性能和细节。线程或者线程组以及线程状况可以被所有的视图选择。所有的视图都可以聚集到方法、类、包或J2EE组件等不同层上。

  • 访问树 Call Tree:显示一个积累的自顶向下的树,树中包含所有在JVM中已记录的访问队列。JDBC,JMS和ZNDI服务请求都被注释在请求树中。请求树可以根据Servlet和JSP对URL的不同需要进行拆分。
  • 热点 Hot Spots:显示消耗时间最多的方法的列表。对每个热点都能够显示回溯树。该热点可以按照方法请求,JDBC,JMS和JNDI服务请求以及按照URL请求来进行计算。
  • 访问图 Call Graph:显示一个从已选方法、类、包或J2EE组件开始的访问队列的图。
  • 方法统计Method Statistis:显示一段时间内记录的方法的调用时间细节。

线程视图threads

JProfiler通过对线程历史的监控判断其运行状态,并监控是否有线程阻塞产生,还能将一个线程所管理的方法以树状形式呈现。对线程剖析。

  • 线程历史 Thread History:显示一个与线程活动和线程状态在一起的活动时间表。
  • 线程监控Thread Monitor:显示一个列表,包括所有的活动线程以及它们目前的活动状况。
  • 线程转储 Thread Dumps:显示所有线程的堆栈跟踪。

线程分析主要关心三个方面:

  1. web容器的线程最大数。比如:Tomcat的线程容量应该略大于最大并发数。
  2. 线程阻塞
  3. 线程死锁

监视器和锁Monitors & locks

监控和锁 Monitors & Locks 所有线程持有锁的情况以及锁的信息。
观察JVM的内部线程并查看状态:

  • 死锁探测图表Current Locking Graph:显示JVM中的当前死锁图表。
  • 目前使用的监测器Current Monitors:显示目前使用的监测器并且包括它们的关联线程。
  • 锁定历史图表 Locking History Graph:显示记录在JVM中的锁定历史。
  • 历史检测记录 Monitor History:显示重大的等待事件和阻塞事件的历史记录。
  • 监控器使用统计 Monitor Usage Statistics:显示分组监测,线程和监测类的统计监测数据

案例分析

案例1

测试代码:

package priv.jvm;import java.util.ArrayList;
import java.util.concurrent.TimeUnit;/*** Description:* Author:江洋大盗* Date:2021/3/22 16:24*/
public class JProfilerTest {public static void main(String[] args) {while (true) {ArrayList<Data> list = new ArrayList<>();for (int i = 0; i < 500; i++) {list.add(new Data());}try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}
}class Data {private final int size = 10;private final byte[] buffer = new byte[1024 * 1024];//1Mprivate final String info = "I love China";
}

点击这里查看宋红康老师的分析过程

案例2

测试代码:

package priv.memoryleak;import java.util.ArrayList;
import java.util.concurrent.TimeUnit;/*** Description:* Author:江洋大盗* Date:2021/3/22 16:43*/
public class MemoryLeak {public static void main(String[] args) {while (true) {ArrayList<Bean> list = new ArrayList<>();for (int i = 0; i < 500; i++) {Bean bean = new Bean();bean.list.add(new byte[1024]);//1KBlist.add(bean);}try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}
}class Bean {private final int size = 100;private final String info = "I love China";static ArrayList<byte[]> list = new ArrayList<>();
}

点击这里查看宋红康老师的案例讲解

Arthas

基本概述

背景
前面,我们介绍了jdk自带的jvisualvm等免费工具,以及商业化工具Jprofiler。
jvisualvm界面

JProfiler界面

这两款工具在业界知名度也比较高,他们的优点是可以图形界面上看到各维度的性能数据,使用者根据这些数据进行综合分析,然后判断哪里出现了性能问题。

但是这两款工具也有个缺点,都必须在服务端项目进程中配置相关的监控参数。然后工具通过远程连接到项目进程,获取相关的数据。这样就会带来一些不便,比如线上环境的网络是隔离的,本地的监控工具根本连不上线上环境。并且类似于Jprofiler这样的商业工具,是需要付费的。

那么有没有一款工具不需要远程连接,也不需要配置监控参数,同时也提供了丰富的性能监控数据呢?

今天跟大家介绍一款阿里巴巴开源的性能分析神器Arthas(阿尔萨斯)

概述
Arthas(阿尔萨斯)是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。

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提供了一些常见的字节码转换和分析算法,可以从它们构建定制的复杂转换和代码分析工具。ASA提供了与其他Java字节码框架类似的功能,但是主要关注性能。因为它被设计和实现得尽可能小和快,所以非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)

官方使用文档

安装与使用

具体安装过程查看参照宋红康老师视频

相关诊断指令

可以参考官方给的教程,因为这是阿里巴巴旗下的开源软件,所以官方文档是中文版的,比较友好。
也可以参考宋红康老师讲解的指令使用

Java Mission Control

历史

在Oracle 收购 Sun之前,Oracle 的 JRockit 虚拟机提供了一款叫做JRockit Mission Control 的虚拟机诊断工具。

在Oracle收购Sun之后,Oracle公司同时拥有了Sun Hotspot和Rockit两款虚拟机。根据oracle对于Java的战略,在今后的发展中,会将JRockit的优秀特性移植到Hotspot上。其中,一个重要的改进就是在Sun的JDK中加入了JRockit的支持。

在Oracle JDK 7u40之后,Mission Control这款工具已经绑定在Oracle JDK中发布。

自Java 11 开始,本节介绍的JFR已经开源。但在之前的 Java版本,JFR属于Commercial Feature,需要通过Java 虚拟机参数
-XX:+UnlockCommercialFeatures开启

如果你有兴趣请可以查看open3DK的Mission Control项目

启动

Mission 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多了)。

功能:实时监控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}

Java Flight Recorder

Java Flight Recorder是 JMC的其中一个组件。

Java Flight Recorder能够以极低的性能开销收集 Java 虚拟机的性能数据。

JFR的性能开销很小,在默认配置下平均低于1%。与其他工具相比,JFR能够直接访问虚拟机内的数据,并且不会影响虚拟机的优化。因此,它非常适用于生产环境下满负荷运行的Java程序。

Java Flight Recorder和JDK Mission Control共同创建了一个完整的工具链。JDK Mission Control可对Java Flight Recorder连续收集低水平和详细的运行时信息进行高效,详细的分析。

事件类型

当启用时,JFR将记录运行过程中发生的一系列事件。其中包括 Java层面的事件,如线程事件、锁事件,以及Java 虚拟机内部的事件,如新建对象、垃圾回收和即时编译事件。

按照发生时机以及持续时间来划分,JFR的事件共有四种类型,它们分别为以下四种。

  1. 瞬时事件(Instant Event),用户关心的是它们发生与否,例如异常、线程启动事件。
  2. 持续事件(Duration Event),用户关心的是它们的持续时间,例如垃圾回收事件。
  3. 计时事件(Timed Event),是时长超出指定阙值的持续事件。
  4. 取样事件(Sample Event),是周期性取样的事件。

取样事件的其中一个常见例子便是方法抽样(Method Sampling),即每隔一段时间统计各个线程的栈轨迹。如果在这些抽样取得的栈轨迹中存在一个反复出现的方法,那么我们可以推测该方法是热点方法。

启动方式

  • 方式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
  • 方式2:使用jcmd的JFR.*子命令

    • 通过jcmd来让JFR开始收集数据、停止收集数据,或者保存所收集的数据,对应的子命令分别为FR.start,JFR.stop,以及JFR.dump。
      $ jcmd < PID >JFR.start settings=profile maxage=10m maxsize=150m name=SomeLabel
      上述命令运行过后,目标进程中的JFR已经开始收集数据。此时,我们可以通过下述命令来导出已经收集到的数据:
      $jcmd JFR.dump name=SomeLabel filename=myrecording.jfr
      最后,我们可以通过下述命令关闭目标进程中的JFR:
      $jcmd JFR.stop name=SomeLabel|
  • 方式3:JMC的JFR插件

Java Flight Recorder取样分析

要采取取样,必须先添加参数:

  • -XX:+UnlockCommercialFeatures
  • -XX:+FlightRecorder

否则

取样时间默认1分钟,可自行按需调整,事件设置选为 profiling,然后可以设置取样profile哪些信息,比如:

  • 加上对象数量的统计:Java Virtual Machine --> GC --> Detailed --> Object Count/0bject Count after GC
  • 方法调用采样的间隔从10ms改为 1ms(但不能低于1ms,否则会影响性能了): Java Virtual Machine -> Profiling -> Method Profiling Sample/Method Sampling Information
  • Socket 与 File采样,10ms太久,但即使改为 1ms也未必能抓住什么,可以干脆取消掉:Java Application->File Read/Filewrite/Socket Read/Socket write


然后就开始 Profile,到时间后 Profile结束,会自动把记录下载回来,在JMC 中展示。

其他工具

Flame Graphs(火焰图)

在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直戏的展示cpu在程序整个生命周期过程中时间分配的工具。

火焰图对于现代的程序员不应该陌生,这个工具可以非常直观的显示出调用栈中的CPU消耗瓶颈。

网上的关于java火焰图的讲解大部分来自于Brendan Gregg的博客:

火焰图:简单通过x轴横条宽度来度量时间指标,y轴代表线程栈的层次。

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在其私人博客《Low Overhead Method Profiling with Java Mission Control》下的评论中曾明确指出JRMC并不支持TOP方法的统计。

TProfiler的下载

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 beused to dynamically trace a running Java program (similar to DTrace for OpenSolaris applications and os). BTrace dynamically instruments the classesof the target application to inject tracing code (“bytecode tracing”)。

简洁明了,大意是一个Java平台的安全的动态追踪工具。可以用来动态地追踪一个运行的Java程序。BTrace动态调整目标应用程序的类以注入跟踪代码(“字节码跟踪”)。

Yourkit and Jprobe and Spring Insight

有兴趣可以去了解

结语

不是每个人都能成为自己想要的样子,但是每个人都可以努力,成为自己自己想要的样子。

JVM监控及诊断工具之GUI篇相关推荐

  1. JVM 学习笔记二十五、JVM监控及诊断工具-命令行篇

    二十五.JVM监控及诊断工具-命令行篇 1.概述 性能诊断是软件工程师在日常工作中经常面对和解决的问题,在用户体验至上的今天,解决好应用软件的性能问题能带来非常大的收益. Java作为最流行的编程语言 ...

  2. 14、JVM监控及诊断工具-命令行篇

    文章目录 第1章.概述 1.简单命令行工具 第2章.jps:查看正在运行的Java进程 1.基本情况 2.基本语法 [1]options参数 [2]hostid参数 第3章.jstat:查看JVM统计 ...

  3. JVM监控及诊断工具命令行篇之jps

    目录 一.概述 二.jps:查看正在运行的Java进程 一.概述 性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益. Java 作 ...

  4. JVM监控及诊断工具-命令行篇一

    1 jps 基本概述 jps(java process status),用于查看正在运行的java虚拟机进程,会显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息).这里说明一下,对于本 ...

  5. 性能监控与调优篇之【3. JVM 监控及诊断工具-GUI 篇】

    文章目录 3. JVM 监控及诊断工具-GUI 篇 3.1. 工具概述 3.2. JConsole 3.3. Visual VM 3.4. Eclipse MAT 3.5. JProfiler 3.6 ...

  6. JVM监控及诊断工具-GUI篇

    3.JVM监控及诊断工具-GUI篇 一.工具概述 使用上一章命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们存在下列局限: 1.无法获取方法级别的分析数据,如方法间的调用关系.各方 ...

  7. 20.JVM监控以及诊断工具-GUI篇

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 20. JVM监控及诊断工具-GUI篇 20.1. 工具概述 使用上一章命令行工具或组合能帮您获取目标Java应用性能相 ...

  8. 干货满满【JVM监控及诊断工具-GUI篇】

    [JVM监控及诊断工具-GUI篇] 3.1. 工具概述 使用上一章命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们存在下列局限: 1.无法获取方法级别的分析数据,如方法间的调用关系 ...

  9. JVM 学习笔记二十六、JVM监控及诊断工具-GUI篇

    二十六.JVM监控及诊断工具-GUI篇 1.工具概述 使用上一张命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但他们存在下列局限: (1)无法获取方法级别的分析数据,如方法间的调用关系 ...

最新文章

  1. python中tk窗口刷新_80 行 Python 代码写个图形计算器
  2. Visual Studio中没有为此解决方案配置选中要生成的项目
  3. 交换机无法ping通之谜
  4. 聚焦和增强卷积神经网络
  5. 浅谈DevExpress六:为chart创建动态数据源
  6. 首发 | 腾讯把需求和代码统一的内幕
  7. 深度学习(三十二)——AlphaGo, AlphaStar
  8. 123. 买卖股票的最3佳时机 III
  9. VueJS ajax综合案例
  10. 迁移桌面程序到MS Store(8)——通过APPX下载Win32Component
  11. https及核心SSL
  12. 文本分类(一)封装分词器
  13. 表达式求值(nyoj305)
  14. linux下编程epoll实现将GPS定位信息上报到服务器
  15. c语言gui程序,GUI编程
  16. ggplot2:可视化设计师的神器,了解一下
  17. linux7重启network,mmp的 rhel7 network重启一直失败,求原因!!!
  18. 计算机科学导论有关论文,计算机科学导论论文
  19. 和“目标”相关的名言
  20. java excel生成_两种方式实现java生成Excel

热门文章

  1. CSS是什么?CSS样式规则
  2. 人工智能的本质居然是……网瘾少年?
  3. 据称百度网络交易平台定名“有啊”
  4. 程序员生活_买卖二手电脑需要注意什么
  5. 6-7 十进制转换二进制(15 分)
  6. suse linux关机命令行,suse linux 关机命令
  7. 乡村少年宫计算机课程,乡村学校少年宫计算机课程安排(5页)-原创力文档
  8. 打开ftp服务器上的文件夹发生错误 请检查是否有权向访问该文件夹
  9. C++为什么始终无法取代 C 吗?
  10. 【人工智能 Open AI】设计一套健身减脂方案,实现100天瘦40斤。