性能分析常用的有以下几种方式 :
监视: 一种用来查看应用程序运行时行为的一般方法。通常会有多个视图(View)分别实时地显示 CPU 使用情况、内存使用情况、线程状态以及其他一些有用的信息,以便用户能很快地发现问题的关键所在。
转储(dump):性能分析工具从内存中获得当前状态数据并存储到文件用于静态的性能分析。Java 程序是通过在启动 Java 程序时添加适当的条件参数来触发转储操作的。它包括以下三种: 核心dump:JVM 生成的本地系统的转储。 Jvm dump:JVM 内部生成的格式化后的数据,包括线程信息,类的加载信息以及堆的统计数据。通常也用于检测死锁。 堆dump:JVM 将所有对象的堆内容存储到文件。
快照:应用程序启动后,性能分析工具开始收集各种运行时数据,其中一些数据直接显示在监视视图中,而另外大部分数据被保存在内部,直到用户要求获取快照,基于这些保存的数据的统计信息才被显示出来。快照包含了应用程序在一段时间内的执行信息,通常有 CPU 快照和内存快照两种类型。 CPU 快照:主要包含了应用程序中函数的调用关系及运行时间,这些信息通常可以在 CPU 快照视图中进行查看。 内存快照:主要包含了内存的分配和使用情况、载入的所有类、存在的对象信息及对象间的引用关系等。这些信息通常可以在内存快照视图中进行查看。
性能分析:性能分析是通过收集程序运行时的执行数据来帮助开发人员定位程序需要被优化的部分,从而提高程序的运行速度或是内存使用效率,主要有以下三个方面:
CPU 性能分析:主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。通常有 CPU 监视和 CPU 快照两种方式来显示 CPU 性能分析结果。
内存性能分析:主要目的是通过统计内存使用情况检测可能存在的内存泄露问题及确定优化内存使用的方向。通常有内存监视和内存快照两种方式来显示内存性能分析结果。
线程性能分析:主要用于在多线程应用程序中确定内存的问题所在。一般包括线程的状态变化情况,死锁情况和某个线程在线程生命期内状态的分布情况等
默认情况下,启动 VisualVM 之后,就会自动监控本地的 Java 进程。但是,如果我们想要监控远程的 Java 进程,则需要进行配置。右键点击Remote
,选择Add Remote Host
,进入如下页面:
在Host name
框中输入我们想要监控的远程主机的 IP,如172.12.21.234
;至于Display name
,我们可以理解为别名或者昵称,自定义即可,完成后,点击OK
按钮:
连接到指定的主机之后,我们还需要指定想要监控的端口。右键点击已连接的主机,选择Add JMX Connection
,新增 Java 管理扩展连接,进入如下页面:
在Connection
中默认会回显主机 IP,我们只需要输入想要监控的端口即可,在这里,我们指定端口为25600
,输入完成后,点击OK
按钮:
VisualVM 已经连接到我们指定的主机以及端口。其中,在上图的右侧部分,已经显示了进程的部分信息,如 PID、Host、Main class、Java 版本和 JVM 启动参数等。但是在这里,需要特别注意一点,那就是:输入的25600
端口并不是随便输入的,而是需要事先在启动脚本或者启动参数中配置的
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.port=25600 \
其中:
-
Thread Dump
,获取线程转储; -
Head Dump
,获取堆转储; -
Application Snapshot
,获取应用运行状态快照。
VisualVM 的右侧页面,包括 Overview、Monitor、Threads、Sampler 和 Visual GC 等,其中:
-
Overview
,进程信息概览,包括 JDK 版本、JVM 启动参数和环境变量配置等信息; -
Monitor
,图形化监控页面,包括 CPU、内存、类以及线程等信息,可以手动触发 GC 以及执行堆转储; -
Threads
,线程信息,可以查询进程内线程活动情况,可以执行线程转储; -
Sampler
,采样器,可以实时采集 CPU、内存等信息; -
Visual GC
,监控垃圾收集情况,想要使用此功能需要我们事前在启动脚本或者启动命令中进行配置。
就是 Monitor 页面的内容,包括 CPU、Memory、Classes 和 Threads,其中:
-
CPU
,实时显示 CPU 使用率以及 GC 活动比例; -
Memory
,实时显示堆使用情况,包括Metaspace
,JDK 8 之前是PermGen
; -
Classes
,实时显示类加载情况; -
Threads
,实时显示线程的数量,包括总线程数量以及守护线程数量。
也可以点击Perform GC
按钮,手动触发 GC;点击Heap Dump
按钮,可以导出堆转储信息。
Sampler
可以采集 CPU 和内存的信息。当我们点击CPU
或者Memory
按钮之后,开始执行采样。
OOM的例子
public class OomTester {
public static void main(String[] args) {
List<String> lists = new ArrayList();
Random random = new Random();
new Thread(() -> {
while (true) {
lists.add(new String(random.nextInt(10000000) + "sssssssssssss"));
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--add object-----");
}
}).start();
}
}
由于线程不断的创建对象,堆内存不断被消耗,当堆内存不足时会触发GC,所以CPU视图可以看到频繁的进行GC活动,而堆内存视图可以看到,堆内存使用越来越高,GC过后变低,最后又升高,直到最后堆内存无法满足GC开销,导致OOM。