第二十三章-JVM监控及诊断工具-GUI篇

  1. 一、工具概述
    1. 1. JDK自带的工具
    2. 2. 第三方工具
    3. 3. 服务端开启远程监控
      1. 3.1 JMX
        1. 3.1.1 无密码方式开启JMX远程连接
        2. 3.1.2 有密码方式开启JMX远程连接
  2. 二、JConsole
    1. 1. 基本概述
    2. 2. 启动
    3. 3. 连接
      1. 3.1 本地连接
      2. 3.2 远程连接
      3. 3.3 Advanced
    4. 4. 主要作用
      1. 4.1 概览
      2. 4.2 内存
      3. 4.3 线程
        1. 4.3.1 根据线程检测死锁
      4. 4.4 加载的类
      5. 4.5 VM概要
  3. 三、Visual VM
    1. 1. 基本概述
    2. 2. 使用方式:
    3. 3. 主要功能
    4. 2. 插件的安装
    5. 3. 连接方式
      1. 3.1 本地连接
      2. 3.2 远程连接
    6. 4. 主要功能
      1. 4.1 生成/读取堆内存快照
        1. 4.1.1 生成dump文件
        2. 4.1.2 导入dump文件
      2. 4.2 查看JVM参数和系统属性
      3. 4.3 查看运行中的虚拟机进程
      4. 4.4 生成/读取线程快照
        1. 4.4.1 生成线程快照
        2. 4.4.2 装入线程快照
      5. 4.5 程序资源的实时监控
      6. 4.6 其他功能
  4. 四、Eclipse MAT
    1. 1. 基本概述
      1. 1.1 安装MAT
    2. 2. 获取堆dump文件
      1. 2.1 生成dump文件
    3. 3. 分析堆dump文件
      1. 3.1 histogram(直方图)
      2. 3.2 thread overview
      3. 3.3 获得对象互相引用的关系
      4. 3.4 浅堆与深堆
        1. 3.4.1 shallow heap(浅堆)
        2. 3.4.2 retained heap(保留集)
        3. 3.4.3 补充:对象实际大小
        4. 3.4.4 案例分析:StudentTrace
      5. 3.5 支配树
    4. 4. 案例:Tomcat堆溢出分析
      1. 4.1 说明
      2. 4.2 分析过程
  5. 五、JProfiler
    1. 1. 基本概述
      1. 1.1 介绍
      2. 1.2 特点
      3. 1.3 主要功能
    2. 2. 安装与配置
      1. 2.1 下载与安装
      2. 2.2 JProfiler 中配置 IDEA
      3. 2.3 IDEA集成JProfiler
    3. 3. 具体使用
      1. 3.1 数据采集方式
      2. 3.2 遥感监测 Telemetries
        1. 3.2.1 概览
        2. 3.2.2 内存
        3. 3.2.3 记录的对象
        4. 3.2.4 记录的吞吐量
        5. 3.2.5 GC活动
        6. 3.2.6 类
        7. 3.2.7 线程
        8. 3.2.8 CPU负载
      3. 3.3 内存视图 Live Memory
        1. 3.3.1 所有对象 All Objects
        2. 3.3.2 记录对象Recorded Objects
        3. 3.3.3 分配访问树 Allocation Call Tree
        4. 3.3.4 分配热点 Allocation Hot Spots
        5. 3.3.5 类追踪器 Class Tracker
      4. 3.4 堆遍历 heap walker
      5. 3.5 cpu视图 cpu views
      6. 3.6 线程视图 threads
      7. 3.7 监视器&锁 Monitors&locks
    4. 4. 案例分析
  6. 六、Arthas
    1. 1. 基本概述
      1. 1.1 背景
      2. 1.2 概述
      3. 1.3 基于哪些工具开发而来
      4. 1.4 官方使用文档
    2. 2. 安装与使用
      1. 2.1 安装
      2. 2.2 工程目录
      3. 2.3 启动
      4. 2.4 查看进程
      5. 2.5 查看日志
      6. 2.6 参看帮助
      7. 2.7 web console
      8. 2.8 退出
    3. 3. 相关诊断指令
      1. 3.1 基础指令
      2. 3.2 jvm相关
      3. 3.3 class/classloader相关
      4. 3.4 monitor/watch/trace相关
      5. 3.5 其他
  7. 七、Java Misssion Control
    1. 1. 历史
    2. 2. 概述
    3. 3. 启动
    4. 4. 功能:实时监控JVM运行时的状态
    5. 5. Java Flight Recorder
      1. 5.1 事件类型
      2. 5.2 启动方式
      3. 5.3 Java Flight Recorder 取样分析
  8. 八、其他工具
    1. 1. Flame Graphs(火焰图)
    2. 2. Tprofiler
    3. 3. Btrace
    4. 3. YourKit
    5. 4. JProbe
    6. 5. Spring Insight

一、工具概述

使用命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们存在下列局限:

  • 无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。

  • 要求用户登录到目标 Java 应用所在的宿主机上,使用起来不是很方便。

  • 分析数据通过终端输出,结果展示不够直观。

为此,JDK提供了一些内存泄漏的分析工具,如 jconsole,jvisualvm 等,用于辅助开发人员定位问题,但是这些工具很多时候并不足以满足快速定位的需求。所以这里我们介绍的工具相对多一些、丰富一些。

1. JDK自带的工具

  • jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等

  • Visual VM:Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。

  • JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。

2. 第三方工具

  • MAT:MAT(Memory Analyzer Tool)是基于 Eclipse 的内存分析工具,是一个快速、功能丰富的 Java heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗

  • JProfiler:商业软件,需要付费。功能强大。

  • Arthas:Alibaba 开源的 java 诊断工具。

  • Btrace:java 运行时追踪工具。

3. 服务端开启远程监控

Jconsole、JVisual、JMC 可视化工具,调用本地监控直接使用对应的命令行即可,但 Linux 无法使用可视化工具,Java 程序基本都部署到 Linux 服务器。需要开启 远程调用服务器,可以使用JMX代理实现.

3.1 JMX

JMX (Java Management Extensions) 是 Java 提供的一套标准 API,用于管理和监控 Java 应用程序的各种性能指标和使用情况。这里主要使用远程访问的功能。

JMX 启动参数:

  • -Dcom.sun.management.jmxremote 远程开启开关
  • -Dcom.sun.management.jmxremote.port=1808 jmx远程调用端口
  • -Dcom.sun.management.jmxremote.rmi.port=1808 添加 rmi 端口
    • JMX 和 RMI,是两种相关联的技术,JMX 使用 RMI 作为远程管理工具来管理和监控 Java 程序,RMI 为 JMX 提供了远程连接所需的远程调用和通信机制。
  • -Dcom.sun.management.jmxremote.authenticate=false 不开启验证
  • -Dcom.sun.management.jmxremote.ssl=false 不为ssl连接
  • -Djava.rmi.server.hostname=192.168.6.203 服务器所在ip或者域名
  • -Dcom.sun.management.jmxremote.pwd.file=/opt/module/jdk1.8.0_261/jre/lib/management/jmxremote.password 配置JMX远程连接用户名和密码

3.1.1 无密码方式开启JMX远程连接

jar 包程序启动一般为:

java -jar xxx.jar

添加参数后:

java -Xmx512m -Xms512m -Xmn512m \
	-Dcom.sun.management.jmxremote \
	-Dcom.sun.management.jmxremote.port=1808 \
	-Dcom.sun.management.jmxremote.rmi.port=1808 \
	-Dcom.sun.management.jmxremote.authenticate=false \
	-Dcom.sun.management.jmxremote.ssl=false \
	-Djava.rmi.server.hostname=192.168.6.203 \
	-jar xxx.jar \
	--spring.profiles.active=prod

启动服务后,使用 Jconsole、Visual VM、JProfiler 等连接服务端, 这里以 Jconsole 连接监控远程服务举例:

Jconsole无密码远程连接

Jconsole无密码远程连接2

Jconsole无密码远程连接3

3.1.2 有密码方式开启JMX远程连接

配置JMX密码文件

# 进入JMX配置文件目录
cd /opt/module/jdk1.8.0_261/jre/lib/management

# 复制JMX密码文件
cp jmxremote.password.template jmxremote.password

# 编辑密码文件, 修改 连接用户名/密码(这里设置为 controlRole / 123456)
vim jmxremote.password

# 保存密码配置文件
wq!

# 配置权限文件为600
chmod 600 jmxremote.password jmxremote.access

jmxremote.password

到这里密码配置就结束了

启动Java服务

java -Xmx512m -Xms512m -Xmn512m \
	-Dcom.sun.management.jmxremote \
	-Dcom.sun.management.jmxremote.port=1808 \	
	-Dcom.sun.management.jmxremote.rmi.port=1808 \
	-Dcom.sun.management.jmxremote.authenticate=true \
	-Dcom.sun.management.jmxremote.ssl=false \
	-Djava.rmi.server.hostname=192.168.6.203 \
	-Dcom.sun.management.jmxremote.pwd.file=/opt/module/jdk1.8.0_261/jre/lib/management/jmxremote.password \
	-jar ioms-ums.jar \
	--spring.profiles.active=dev

使用Jconsole、Visual VM、JProfiler 等连接服务端, 这里以 Jconsole 连接监控远程服务举例:

Jconsole有密码远程连接

注意: 服务器防火墙要开放JMX监控端口

二、JConsole

1. 基本概述

2. 启动

  • 方式一: 在jdk安装目录中找到jconsole.exe,双击该可执行文件就可以
  • 方式二: 打开DOS窗口,直接输入jconsole就可以了

3. 连接

3.1 本地连接

注意:本地连接要求 启动jconsole的用户 和 运行当前程序的用户 是同一个用户

具体操作如下:

  1. 在jdk的 bin 目录下,双击 jconsole.exe , 即可启动 jconsole

启动jconsole

  1. 选择 本地进程 , 选择要监控的程序 PID , 点击 连接

选择本地进程

  1. 点击 不安全的连接

不安全的连接

  1. 进入控制台页面

控制台页面

3.2 远程连接

见上一章节,第三部分 : 服务端开启远程监控

3.3 Advanced

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

4. 主要作用

4.1 概览

概览

4.2 内存

内存

可以在这里点 执行GC 手动进行一次 GC操作.

4.3 线程

监控程序线程

4.3.1 根据线程检测死锁

在线程监控中点击 检测死锁 检查程序中是否线程发生了 死锁

检测死锁

4.4 加载的类

加载的类

4.5 VM概要

VM概要

三、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。
  • 在JDK6 Update 7以后,Visua1 VM 便作为JDK的一部分发布( Visual VM 在 JDK/bin 目录下),即:它完全免费。
  • 此外,Visual VM 也可以作为独立的软件安装:

首页: https://visualvm.github.io/index.html

Visual VM 官网

2. 使用方式:

  • 方式一: 在jdk安装目录中找到 jvisualvm.exe ,然后双击执行即可

    jvisualvm.exe

  • 方式二: 打开DOS窗口,输入 jvisualvm 就可以打开该软件

3. 主要功能

  • 生成/读取堆内存/线程快照

  • 查看JVM参数和系统属性

  • 查看运行中的虚拟机进程

  • 程序资源的实时监控

  • JMX代理连接、远程环境监控、CPU分析和内存分析

2. 插件的安装

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

  • 离线安装

插件地址 : https://visualvm.github.io/pluginscenters.html

选择Visual VM 版本

插件下载中心

点击需要的插件, 即可下载.

点击添加插件

选择插件

点击安装

安装成功

  • 在线安装

在线安装

  • IDEA 安装 VisualVM Launcher 插件

“File” →→ “Settings” →→ “Plugins” 打开插件安装界面。

安装VisualVM插件

插件安装好之后,重启IDEA,然后再打开 “Settings” 配置界面,找到 “VisualVM Launcher”,配置 Visual VM 的路径和JDK,如下:

配置Visual VM 插件

IDEA 启动并连接 Visual VM

IDEA 启动并连接 Visual VM

3. 连接方式

3.1 本地连接

打开 Visual VM , 选择一个运行中的本地进程即可.

Visual VM 本地连接

3.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中输入端口号、用户名、密码登录

实操远程连接

  • 服务器设置开启 JMX 远程监控端口

    • 具体参考 第一章节: 服务端开启远程监控
  • Visual VM 添加远程连接

    添加JMX连接

设置JMX连接参数

JMX连接成功

4. 主要功能

4.1 生成/读取堆内存快照

4.1.1 生成dump文件

  • 方式一: 在左侧列表, 右击运行的进程, 直接生成 堆Dump

    直接生成 堆Dump

  • 方式二: 在 监视器TAB 标签页中, 点击 堆Dump 生成dump文件

    监视器生成dump

注意:
生成堆内存快照如下图:

生成的dump文件

这些快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:

导出dump文件

4.1.2 导入dump文件

导入dump文件

选择要导入的dump文件

选择dump文件

4.2 查看JVM参数和系统属性

JVM参数和系统属性

4.3 查看运行中的虚拟机进程

查看进程

其中每个进程号都代表一个进程

4.4 生成/读取线程快照

4.4.1 生成线程快照

生成线程快照

生成线程快照结果

4.4.2 装入线程快照

装入线程快照

选择快照文件

4.5 程序资源的实时监控

程序资源的实时监控

  • 可以同时查看 CPU、线程、堆 的使用情况
  • 可以查看类的加载数量
  • 可以手动点击进行一次 GC

4.6 其他功能

  • JMX代理连接
  • 远程环境监控
  • CPU分析和内存分析

四、Eclipse MAT

1. 基本概述

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

MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。大家可以在 https://www.eclipse.org/mat/downloads.php 下载并使用MAT

1.1 安装MAT

  • 打开官网, 下载 MAT, 注意: 我的JDK是 1.8版本的, 需要下载 MAT 1.11.0 版本

image-20240818124732612

  • 解压安装包, 双击 MemoryAnalyzer.exe 即可启动

    启动MAT

  • 解决JDK版本不兼容问题

    JDK版本不兼容

    打开MAT的安装目录,找到配置文件 MemoryAnalyzer.ini

    MemoryAnalyzer.ini

    打开这个文件,在文件中如下两行,指定jdk版本。

    指定jdk版本

    注意事项

    • 写在开头
    • 必须小写-vm
    • 必须 换行 写成2行

    保存之后,再次尝试,顺利启动。

还可以在 Eclipse 中以插件的方式安装

eclipse安装MAT插件

2. 获取堆dump文件

MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的 hprof 文件,通过 MAT 打开就可以直观地看到当前的内存信息。

一般说来,这些内存信息包含:

  • 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。
  • 所有的类信息,包括 classloader、类名称、父类、静态变量等
  • GCRoot 到所有的这些对象的引用路径
  • 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)

两点说明:

  • 说明1:

    缺点: MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如 Sun,HP,SAP 所采用的 HPROF 二进制堆存储文件,以及 IBM 的 PHD 堆存储文件等都能被很好的解析。

  • 说明2:

    最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现。

    生成内存泄漏报表

2.1 生成dump文件

  • 方法一: 通过 jmap 工具生成,可以生成任意一个java进程的 dump 文件;

  • 方法二: 通过配置JVM参数生成。

    • 选项 “ -XX:+HeapDumpOn0utOfMemoryError “或” -XX:+HeapDumpBeforeFullGc “
    • 选项 “ -XX:HeapDumpPath “ 所代表的含义就是当程序出现 OutofMemory 时,将会在相应的目录下生成一份 dump 文件。如果不指定选项 “ -XX:HeapDumpPath ”则在当前目录下生成dump文件。

    对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用 jmap + MAT 工具是最常见的组合。

  • 方法三: 使用 VisualVM 可以导出堆dump文件

  • 方法四: 使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动Java程序中导出堆快照该功能将借助jps列出当前正在运行的 Java 进程,以供选择并获取快照。

    MAT导出dump文件

3. 分析堆dump文件

3.1 histogram(直方图)

展示了各个类的实例数目以及这些实例的 Shallow heap 或者 Retained heap 的总和

生成直方图

点击上面两个红框其中任意一个,都可以生成直方图,生成具体内容:

直方图内容

3.2 thread overview

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

线程概览

线程明细

3.3 获得对象互相引用的关系

  • with outgoing references 变量引用了谁

    outgoing

    引用结果

  • with incoming references 谁引用了该变量

    incoming

    引用结果

3.4 浅堆与深堆

3.4.1 shallow heap(浅堆)

shallow heap是对象本身的大小,不包括其引用的对象。
对于非数组类型:shallow heap就是对象与其成员变量加在一起的大小。
对于数组类型:shallow heap就是数组各个元素大小之和。

3.4.2 retained heap(保留集)

对象remainder heap 大小 = 对象shallow heap大小 + 对象直接或者间接引用的对象大小(排除被GC ROOT直接引用的对象)。
如下图所示:

对象引用关系

对象引用关系
OBJECT A 的 remainder heap = OBJECT A 的 shallow heap
OBJECT B的 remainder heap = OBJECT B 的 shallow heap + OBJECT C 的对象大小 + OBJECT E 的对象大小

所以对象的remainder heap其实接近进行垃圾回收时,清除该对象时所能获取的堆内存大小(有的对象可能还被其他对象引用,垃圾回收时,不能清除)。

下面是用mat对从服务器上面dump出来Java虚拟机内存进行解析之后的结果:

内存分析

Java内存解析结果

其中,Objects为类的对象的个数。Shallow Heap是这些对象自身大小之和,Remainder Heap是这些对象自身以及这些对象直接或者间接引用的对象大小总和。

3.4.3 补充:对象实际大小

另外一个常用的概念是对象的实际大小。这里,对象的实际大小定义为一个对象所能触及的所有对象的浅堆大小之和,也就是通常意义上我们说的对象大小。与深堆相比,似乎这个在日常开发中更为直观和被人接受,但实际上,这个概念和垃圾回收无关

下图显示了一个简单的对象引用关系图,对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大小只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小为A与D之和,由于对象C还可以通过对象B访问到,因此不在对象A的深堆范围内。

对象引用关系

3.4.4 案例分析:StudentTrace

案例代码

/**
 * 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。
 * 它由三个部分组成:Student、WebPage和StudentTrace三个类
 *
 *  -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=d:\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个字节

解析:

默认采用了指针压缩则为12字节,没有采用则为16字节(数组还需要加上数组长度)。

1.为什么有152字节和144字节:因为我们的URL和content存在两种情况
URL:”http://www.7.com"、content:"7"-----URL:"http://www.14.com"、content:"14
第一种URL长度为16,底层的char数组的占用空间为(【】方括号里面整个都属于对象头,分开写方便大家理解)
【普通对象头(12) + 数组长度(4)】 + 16个字符(32) = 48字节,符合8字节对齐
同理content 占用 【普通对象头(12) +数组长度(4)】+ 一个字符(2) = 18字节,八字节对齐=24字节
第二种URL长度为17,底层的插入数组的占用空间为
【普通对象头(12) + 数组长度(4)】 + 17个字符(34) = 50字节,不符合8字节对齐,对齐为56
同理content 占用 【普通对象头(12) +数组长度(4)】+ 两个字符(4) = 20字节,八字节对齐=24字节
所以第一种总字节为48 + 24 = 72,第二种总字节为56 + 24 = 80
因此第二种比第一种多了8字节,所以是152和144。
(为什么总大小是152而不是72是因为我们只计算了String底层的char数组的区别没有计算各变量本身的浅堆,
因为结构都相同,所以差别就差在内容的占用上)

2.为什么最终结果是1288
首先ElementData数组本身的浅堆大小为
【普通对象头(12) + 数组长度(4)】 + 数组内容【15个Obejct引用=16*4】 = 76,八字节对齐=80字节
15个Object分为13个152字节+2个144字节,总大小为=2264字节
7号和其他student重复的有0、21、42、63、84、35、70总计6个152和1一个144
所以2264 - 6 * 152 - 144 = 1208字节
所以ElementData本身的浅堆80 + 仅能通过它到达的浅堆1208 = 1288

3.5 支配树

支配树的概念源自图论

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

MAT通过dump文件查看对象支配树

下图显示了对象支配树视图的一部分。该截图显示部分Lily学生的history队列的直接支配对象。即当Lily对象被回收,也会一并回收的所有对象。显然能被3或者5整除的网页不会出现在该列表中,因为它们同时被另外两名学生对象引用。

4. 案例:Tomcat堆溢出分析

4.1 说明

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

4.2 分析过程

Tomcat溢出分析图1

Tomcat溢出分析图2

Tomcat溢出分析图3

Tomcat溢出分析图4

Tomcat溢出分析图5

Tomcat溢出分析图6

Tomcat溢出分析图6-2

Tomcat溢出分析图7

Tomcat溢出分析图8

五、JProfiler

1. 基本概述

1.1 介绍

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

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

官网下载地址:https://www.ej-technologies.com/jprofiler

JProfiler 官网

1.2 特点

  • 使用方便、界面操作友好(简单且强大)

  • 对被分析的应用影响小(提供模板)

  • CPU,Thread,Memory分析功能尤其强大

  • 支持对jdbc,noSql,jsp,servlet,socket等进行分析

  • 支持多种模式(离线,在线)的分析

  • 支持监控本地、远程的JVM

  • 跨平台,拥有多种操作系统的安装版本

1.3 主要功能

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

2. 安装与配置

2.1 下载与安装

下载地址:https://www.ej-technologies.com/jprofiler/download

下载 JProfiler

选择一个版版本,点击下载后,安装。(收费软件,下载安装后有试用期)

或者下载使用破解版本:https://downloadlynet.ir/2020/12/1885/03/jprofiler/00/?#/1885-ej-techn-152438080522.html

2.2 JProfiler 中配置 IDEA

IDEA集成

点击选择 IDEA Integrations

选择IDEA,点击 Integrate

选择 IntelliJ IDEA , 点击 Integrate, 再点击 OK

2.3 IDEA集成JProfiler

安装 JProfiler 插件

IDEA 安装 JProfiler 插件

image-20240822202816158

Tool 中配置 JProfiler 的执行exe文件路径。

安装并配置完成后,IDEA工具栏会出现这两个图标,点击可以直接启动一个进程,或者追踪一个运行中的进程。

image-20240822203120430

3. 具体使用

3.1 数据采集方式

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

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

3.2 遥感监测 Telemetries

3.2.1 概览

概览

3.2.2 内存

内存

内存占用持续飙升,存在OOM风险

3.2.3 记录的对象

记录的对象

3.2.4 记录的吞吐量

记录的吞吐量

3.2.5 GC活动

GC活动

3.2.6 类

类

3.2.7 线程

线程

3.2.8 CPU负载

CPU负载

3.3 内存视图 Live Memory

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

3.3.1 所有对象 All Objects

显示所有加载的类的列表和在堆上分配的实例数。只有Java 1.5(JVMTI)才会显示此视图。

Live Memory

通过内存标记,可以查看到 Picture 持续增加,程序存在内存泄漏。

3.3.2 记录对象Recorded Objects

查看特定时间段对象的分配,并记录分配的调用堆栈。

Recorded Objects

可以看到Picture类占用了过多的内存,需要重点查看

3.3.3 分配访问树 Allocation Call Tree

显示一棵请求树或者方法、类、包或对已选择类有带注释的分配信息的J2EE组件。

分配访问树

3.3.4 分配热点 Allocation Hot Spots

显示一个列表,包括方法、类、包或分配已选类的J2EE组件。你可以标注当前值并且显示差异值。对于每个热点都可以显示它的跟踪记录树。

Allocation Hot Spots

3.3.5 类追踪器 Class Tracker

类跟踪视图可以包含任意数量的图表,显示选定的类和包的实例与时间。

Class Tracker

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

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

注意:

  • All Objects后面的Size大小是浅堆大小
  • Record Objects 在判断内存泄露的时候使用,可以通过观察Telemetries中的Memory,如果里面出现垃圾回收之后的内存占用逐步提高,这就有可能出现内存泄露问题,所以可以使用Record Objects查看,但是该分析默认不开启,毕竟占用CPU性能太多

3.4 堆遍历 heap walker

如果通过内存视图 Live Memory已经分析出哪个类的对象不能进行垃圾回收,并且有可能导致内存溢出,如果想进一步分析,我们可以在该对象上点击右键,选择Show Selection In Heap Walker,如下图:

Show Selection In Heap Walker

进入堆遍历视图,如下图所示:

image-20240823123007846

查看对象的引用关系,如下图:

对象引用关系

  • Outgoing references 对象引用了谁

    Outgoing references

  • Incoming references 谁引用了自己

    Incoming references

  • Show In Graph 通过图表形式查看对象引用关系

    image-20240823124143505

引用关系图

3.5 cpu视图 cpu views

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

  • 访问树 Call Tree

    显示一个积累的自顶向下的树,树中包含所有在JVM中已记录的访问队列。JDBC,JMS和JNDI服务请求都被注释在请求树中。请求树可以根据Servlet和JSP对URL的不同需要进行拆分。

    访问树

上面的 100% 表示该方法会100%调用下面的方法, 276s 表示方法调用一共花费了276秒, 再往下就是方法的全名称.

左上角可以筛选展示: Package, Class, Method

  • 热点 Hot Spots

    显示消耗时间最多的方法的列表。对每个热点都能够显示回溯树。该热点可以按照方法请求,JDBC,JMS和JNDI服务请求以及按照URL请求来进行计算。

    热点

  • 访问图 Call Graph

    显示一个从已选方法、类、包或]2EE组件开始的访问队列的图。

    访问图

  • 方法检测 Outlier Detection

    显示一段时间内记录的方法的调用时间细节。

    image-20240823200244805

  • 调用栈追踪 Call Tracer

    image-20240823200526033

3.6 线程视图 threads

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

  • 线程历史 Thread History

    显示一个与线程活动和线程状态在一起的活动时间表。

    线程历史

  • 线程监控 Thread Monitor

    显示一个列表,包括所有的活动线程以及它们目前的活动状况。

    线程监控

  • 线程转储 Thread Dumps

    显示所有线程的堆栈跟踪。

    线程转储

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

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

3.7 监视器&锁 Monitors&locks

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

  • 死锁探测图表 Current Locking Graph

    显示JVM中的当前死锁图表

    死锁探测图表

  • 监测器 Current Monitors

    显示目前使用的监测器并且包括它们的关联线程

    Current Monitors

  • 锁定历史图表 Locking History Graph

    显示记录在JVM中的锁定历史

  • 历史检测记录Monitor History

    显示重大的等待事件阻塞事件的历史记录

  • 监控器使用统计 Monitor Usage Statistics

    显示分组监测,线程和监测类的统计监测数据

4. 案例分析

  • 代码

    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();
    }
  • 使用JProfiler进行分析

    • 我们通过JProfiler来看一下,如下:

      查看内存

      你可以看到内存一个劲的往上涨,但是就是没有下降的趋势,说明这肯定有问题,过不了多久就会出现OOM,我们来到Live memory中,先标记看一下到底是哪些对象在进行内存增长,等一小下看看会不会触发垃圾回收,如果不触发的话,我们自己来触发垃圾回收,之后观察哪些对象没有被回收掉,如下:

    • 查看Live Memory, 标记内存, 手动进行GC

      查看活动的内存

      我上面点击了Mark Current,发现有些对象在持续增长,然后点击了一下Run GC,发现 byte[] 并没有被回收

    • 点击Show Selection In Heap Walker,如下:

      右键,点击 Heap Walker

    • 然后看一下该对象被谁引用,如下:

      Incoming reference

      结果如下:

      image-20240823205342532

  • 结论

    可以看出byte[]来自于Bean类是的list中,并且这个list是ArrayList类型的静态集合,所以找到了:static ArrayList list = new ArrayList();
    发现list是静态的,这不妥,因为我们的目的是while结束之后Bean对象被回收,并且Bena对象中的所有字段都被回收,但是list是静态的,那就是类的,众所周知,类变量随类而生,随类而灭,因此每次我们往list中添加值,都是往同一个list中添加值,这会造成list不断增大,并且不能回收,所以最终会导致OOM

六、Arthas

1. 基本概述

1.1 背景

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

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

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

阿里巴巴开源的性能分析神器Arthas(阿尔萨斯)不需要远程连接,也不需要配置监控参数,并且提供了丰富的性能监控数据.

1.2 概述

Arthas支持JDK6+,支持 Linux / Mac / Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  • 这个类从哪个jar包加载的?为什么会报各种类相关的Exception?

  • 我改的代码为什么没有执行到?难道是我没commit?分支搞错了?

  • 遇到问题无法在线上 debug , 难道只能通过加日志再重新发布吗?

  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!

  • 是否有一个全局视角来查看系统的运行状况?

  • 有什么办法可以监控到JVM的实时运行状态?

  • 怎么快速定位应用的热点,生成火焰图?

1.3 基于哪些工具开发而来

  • 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字节码框架类似的功能,但是主要关注性能。因为它被设计和实现得尽可能小和快,所以非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)

1.4 官方使用文档

https://arthas.aliyun.com/

官网

2. 安装与使用

2.1 安装

安装方式一: 可以直接在Linux上通过命令下载

可以在官方 Github 上进行下载,如果速度较慢,可以尝试国内的码云 Gitee 下载。

  • Github下载

    wget https://alibaba.github.io/arthas/arthas-boot.jar

    从Github下载

  • Gitee 下载

    wget https://arthas.gitee.io/arthas-boot.jar

安装方式二:

也可以在浏览器直接访问 https://alibaba.github.io/arthas/arthas-boot.jar ,等待下载完后,上传到Linux服务器上。

卸载

在 Linux/Unix/Mac 平台,删除下面的文件:

rm -rf ~/.arthas
rm -rf ~/logs/arthas

image-20240824083153843

Windows 平台直接删除 <User_Home> 下的 .arthas 和 logs/arthas 目录

2.2 工程目录

  • arthas-agent: 基于 JavaAgent 技术的代理
  • bin: 一些启动脚本
  • arthas-boot: Java 版本的一键安装启动脚本
  • arthas-client: telnet client 代码
  • arthas-common: 一些共用的工具类和枚举类
  • arthas-core: 核心库,各种 arthas 命令的交互和实现
  • arthas-demo: 示例代码
  • arthas-memorycompiler: 内存编绎器代码,Fork from https://github.com/skalogs/SkaETL/tree/master/compiler
  • arthas-packaging: maven 打包相关的
  • arthas-site: arthas 站点
  • arthas-spy: 编织到目标类中的各个功面
  • static: 静态资源
  • arthas-testcase: 测试

2.3 启动

Arthas 只是一个 Java 程序,所以可以直接用 java -jar 运行。

  • 执行成功后,arthas 提供了一种命令行方式的交互方式,arthas 会检测当前服务器上的 Java 进程并将进程列表展示出来,用户输入对应的编号 (1、2、3、4…) 进行选择,然后回车。

    选择进程(输入[]内编号(不是PID)回车)

启动方式一

  • 方式2: 运行时选择 Java 进程 PID

启动方式二

2.4 查看进程

查看运行中的程序进程号,使用命令:jps 或者 jps -l

2.5 查看日志

cat ~/logs/arthas/arthas.log

2.6 参看帮助

java -jar arthas-boot.jar -h

2.7 web console

除了在命令行查看外,Arthas目前还支持 Web Console 。在成功启动连接进程之后就已经自动启动,可以直接访问 http://127.0.0.1:8563/ 访问,页面上的操作模式和控制台完全一样。

2.8 退出

  • 使用 quit/exit:退出当前客户端
  • 使用 stop/shutdown:关闭 arthas 服务端,并退出所有客户端。

3. 相关诊断指令

官方文档: https://arthas.aliyun.com/doc/commands.html

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 快捷键列表及自定义快捷键

3.2 jvm相关

https://arthas.aliyun.com/doc/commands.html

  • dashboard: 当前系统的实时数据面板
  • thread: 查看当前 JVM 的线程堆栈信息
  • jvm: 查看当前 JVM 的信息
  • sysenv: 查看 JVM 的环境变量
  • sysprop: 查看和修改 JVM 的系统属性
  • sysem: 查看 JVM 的环境变量
  • vmoption: 查看和修改 JVM 里诊断相关的 option
  • perfcounter: 查看当前 JVM 的 Perf Counter 信息
  • logger: 查看和修改 logger
  • getstatic: 查看类的静态属性
  • ognl: 执行 ognl 表达式
  • mbean: 查看 Mbean 的信息
  • heapdump: dump java heap,类似 jmap 命令的 heap dump 功能

3.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

3.4 monitor/watch/trace相关

请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 stop 或将增强过的类执行 reset 命令。

  • monitor 对匹配 class-pattern/method-pattern/condition-express 的类、方法的调用进行监控。
  • watch: 让你能方便的观察到指定函数的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。
  • trace: trace 命令能主动搜索 class-patternmethod-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
  • stack: 很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
  • tt: 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测

3.5 其他

七、Java Misssion Control

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 开启。

2. 概述

Java Mission Control(简称 JMC) , Java 官方提供的性能强劲的工具,是一个用于对 Java 应用程序进行管理、监视、概要分析和故障排除的工具套件。

它包含一个 GUI 客户端以及众多用来收集 Java 虚拟机性能数据的插件如 JMX Console(能够访问用来存放虚拟机各个子系统运行数据的 MXBeans)以及虚拟机内置的高效 profiling 工具 Java Flight Recorder(JFR)。

JMC 的另一个优点就是:采用取样,而不是传统的代码植入技术,对应用性能的影响非常非常小,完全可以开着 JMC 来做压测(唯一影响可能是 full gc 多了)。

3. 启动

Mission Control 位于 %JAVA_HOME%/bin/jmc.exe

jmc.exe

4. 功能:实时监控JVM运行时的状态

如果是远程服务器,使用前要开 JMX。

JMX 启动参数:

  • -Dcom.sun.management.jmxremote 远程开启开关
  • -Dcom.sun.management.jmxremote.port=1808 jmx远程调用端口
  • -Dcom.sun.management.jmxremote.rmi.port=1808 添加 rmi 端口
    • JMX 和 RMI,是两种相关联的技术,JMX 使用 RMI 作为远程管理工具来管理和监控 Java 程序,RMI 为 JMX 提供了远程连接所需的远程调用和通信机制。
  • -Dcom.sun.management.jmxremote.authenticate=false 不开启验证
  • -Dcom.sun.management.jmxremote.ssl=false 不为ssl连接
  • -Djava.rmi.server.hostname=192.168.6.203 服务器所在ip或者域名
  • -Dcom.sun.management.jmxremote.pwd.file=/opt/module/jdk1.8.0_261/jre/lib/management/jmxremote.password 配置JMX远程连接用户名和密码

服务端启动示例

java -Xmx512m -Xms512m -Xmn512m \
	-XX:+UnlockCommercialFeatures -XX:+FlightRecorder \
	-Dcom.sun.management.jmxremote \
	-Dcom.sun.management.jmxremote.port=1808 \
	-Dcom.sun.management.jmxremote.rmi.port=1808 \
	-Dcom.sun.management.jmxremote.authenticate=false \
	-Dcom.sun.management.jmxremote.ssl=false \
	-Djava.rmi.server.hostname=192.168.6.203 \
	-jar ioms-ums.jar \
	--spring.profiles.active=dev

注意:启动时,要在关闭用户校验的情况下启动JMX:-Dcom.sun.management.jmxremote.authenticate=false,并且添加参数 -XX:+UnlockCommercialFeatures -XX:+FlightRecorder 开启飞行记录仪;

另外说明一点:

这里需要注意的一点是,JMC可以用于java7以上的所有版本,而飞行记录器,只能用于oracle jre,且是java7及以上的版本,因为要使用飞行记录器,需要开启jvm的商业特性,也就是在启动的时候加上参数:”-XX:+UnlockCommercialFeatures”,”-XX:+FlightRecorder”。如果是open jdk,尝试加这两个参数的时候,会直接导致虚拟机终止,无法正常启动。所以,飞行记录器只能局限在oracle jdk里面使用。

JMC连接监控进程

文件 ->连接 ->创建新连接, 填入上面 JMX 参数的 host 和 port, 具体参考:第一章第3节:服务端开启远程监控

远程连接JMC

连接JMC成功

5. Java Flight Recorder

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

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

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

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

5.1 事件类型

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

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

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

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

5.2 启动方式

方式1:使用 -XX:StartFlightRecording=参数

第一种是在运行目标 Java 程序时添加 -XX:startFlightRecording=参数

比如: 下面命令中,JFR 将会在 Java 虚拟机启动 5s 后 (对应 delay=5s) 收集数据,持续 20s(对应 duration=28s)。当收集完毕后,JFR 会将收集得到的数据保存至指定的文件中(对应 filename=myrecording.jfr)

java -Xmx512m -Xms512m -Xmn512m \
	-XX:+UnlockCommercialFeatures -XX:+FlightRecorder \
	-XX:StartFlightRecording=delay=5s,duration=20s,filename=myrecording.jfr,settings=profile \
	-Dcom.sun.management.jmxremote \
	-Dcom.sun.management.jmxremote.port=1808 \
	-Dcom.sun.management.jmxremote.rmi.port=1808 \
	-Dcom.sun.management.jmxremote.authenticate=false \
	-Dcom.sun.management.jmxremote.ssl=false \
	-Djava.rmi.server.hostname=192.168.6.203 \
	-jar ioms-ums.jar \
	--spring.profiles.active=dev

由于 JFR 将持续收集数据,如果不加以限制,那么 JFR 可能会填满硬盘的所有空间。因此,我们有必要对这种模式下所收集的数据进行限制。

比如:

java -Xmx512m -Xms512m -Xmn512m \
	-XX:+UnlockCommercialFeatures -XX:+FlightRecorder \
	-XX:StartFlightRecording=delay=5s,,duration=20s,maxage=10m,maxsize=100m,name=SomeLabel,filename=myrecording.jfr,settings=profile \
	-Dcom.sun.management.jmxremote \
	-Dcom.sun.management.jmxremote.port=1808 \
	-Dcom.sun.management.jmxremote.rmi.port=1808 \
	-Dcom.sun.management.jmxremote.authenticate=false \
	-Dcom.sun.management.jmxremote.ssl=false \
	-Djava.rmi.server.hostname=192.168.6.203 \
	-jar ioms-ums.jar \
	--spring.profiles.active=dev

方式2:使用 jcmd 的JFR.*子命令

通过 jcmd 来让 JFR 开始收集数据、停止收集数据,或者保存所收集的数据,对应的子命令分别为JFR.start, JFR.stop,以及JFR.dump。

  • 启动java程序

    java -Xmx512m -Xms512m -Xmn512m \
    	-XX:+UnlockCommercialFeatures -XX:+FlightRecorder \
    	-jar ioms-ums.jar \
    	--spring.profiles.active=dev
  • 查看程序进程

    root@ubuntu:/opt/module/ioms/ioms-ums# jps -l
    29318 ioms-gateway.jar
    29272 ioms-eureka.jar
    27116 ioms-ums.jar
    27228 sun.tools.jps.Jps
    29422 ioms-auth.ja
  • 通过jcmd来让 JFR 开始收集数据

    $ jcmd <PID> JFR.start settings=profile maxage=10m maxsize=150m name=SomeLabel

    示例:

    jcmd 27116 JFR.start settings=profile maxage=10m maxsize=100m name=test-jfr

    JFR 开始收集数据

  • 保存所收集的数据

    jcmd <PID> JFR.dump name=SomeLabel filename=myrecording.jfr

    示例:

    jcmd 27116 JFR.dump name=test-jfr filename=myrecording.jfr

    保存所收集的数据

  • 关闭目标进程中的 JFR

    $ jcmd <PID> JFR.stop name=SomeLabel

    示例:

    jcmd 27116 JFR.stop name=test-jfr

    关闭目标进程中的 JFR

配置飞行记录仪监测

  1. 启动飞行记录仪

启动飞行记录仪

启动飞行记录仪

2.正式启动

正式启动

启动 Object Count 和 Object Count after GC

正式启动

正式启动

5.3 Java Flight Recorder 取样分析

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

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

JDK9及更高版本

  • -XX:+UnlockCommercialFeatures
  • -XX:+StartFlightRecording

如:-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=200s

否则:

img

img

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

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

img

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

JMC分析文件

八、其他工具

1. Flame Graphs(火焰图)

在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直观的展示CPU在程序整个生命周期过程中时间分配的工具。火焰图对于现代的程序员不应该陌生,这个工具可以非常直观的显示出调用找中的CPU消耗瓶颈。

网上的关于Java火焰图的讲解大部分来自于Brenden Gregg的博客 http://new.brendangregg.com/flamegraphs.html

CPU Flame Graphs

CPU Flame Graphs

Memory Flame Graph

Memory Flame Graph

火焰图,简单通过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 方法的统计。

Github地址:http://github.com/alibaba/Tprofiler

3. Btrace

常见的动态追踪工具有BTrace、HouseHD(该项目己经停止开发)、Greys-Anatomy(国人开发 个人开发者)、Byteman(JBoss出品),注意Java运行时追踪工具井不限干这几种,但是这几个是相对比较常用的。

BTrace是SUN Kenai 云计算开发平台下的一个开源项目,旨在为java提供安全可靠的动态跟踪分析工具。先看一卜日Trace的官方定义:

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 applications and 0s). BTrace dynamically instruments the classes of the target application to inject tracing code (“bytecode tracing” ).

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

3. YourKit

4. JProbe

5. Spring Insight


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 george_95@126.com