@ -4,971 +4,6 @@
[TOC]
# JDK Tools
## jps
用于显示当前用户下的所有java进程信息:
```shell
# jps [options] [hostid]
# q:仅输出VM标识符, m: 输出main method的参数,l:输出完全的包名, v:输出jvm参数
[root@localhost ~]# jps -l
28729 sun.tools.jps.Jps
23789 cn.ms.test.DemoMain
23651 cn.ms.test.TestMain
```
## jstat
用于监视虚拟机运行时状态信息( 类装载、内存、垃圾收集、JIT编译等运行数据) :
**-gc**:垃圾回收统计(大小)
```shell
# 每隔2000ms输出< pid > 进程的gc情况, 一共输出2次
[root@localhost ~]# jstat -gc < pid > 2000 2
# 每隔2s输出< pid > 进程的gc情况, 每个3条记录就打印隐藏列标题
[root@localhost ~]# jstat -gc -t -h3 < pid > 2s
Timestamp S0C S1C S0U S1U ... YGC YGCT FGC FGCT GCT
1021.6 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
1023.7 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
1025.7 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
Timestamp S0C S1C S0U S1U ... YGC YGCT FGC FGCT GCT
1027.7 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
1029.7 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
# 结果说明: C即Capacity 总容量, U即Used 已使用的容量
##########################
# S0C: 年轻代中第一个survivor( 幸存区) 的容量 (kb)
# S1C: 年轻代中第二个survivor( 幸存区) 的容量 (kb)
# S0U: 年轻代中第一个survivor( 幸存区) 目前已使用空间 (kb)
# S1U: 年轻代中第二个survivor( 幸存区) 目前已使用空间 (kb)
# EC: 年轻代中Eden( 伊甸园) 的容量 (kb)
# EU: 年轻代中Eden( 伊甸园) 目前已使用空间 (kb)
# OC: Old代的容量 (kb)
# OU: Old代目前已使用空间 (kb)
# PC: Perm(持久代)的容量 (kb)
# PU: Perm(持久代)目前已使用空间 (kb)
# YGC: 从应用程序启动到采样时年轻代中gc次数
# YGCT: 从应用程序启动到采样时年轻代中gc所用时间(s)
# FGC: 从应用程序启动到采样时old代(全gc)gc次数
# FGCT: 从应用程序启动到采样时old代(全gc)gc所用时间(s)
# GCT: 从应用程序启动到采样时gc用的总时间(s)
```
**-gcutil**:垃圾回收统计(百分比)
```shell
[root@localhost bin]# jstat -gcutil < pid >
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 99.80 16.21 26.18 93.34 90.74 9 0.056 2 0.045 0.102
# 结果说明
##########################
# S0: 年轻代中第一个survivor( 幸存区) 已使用的占当前容量百分比
# S1: 年轻代中第二个survivor( 幸存区) 已使用的占当前容量百分比
# E: 年轻代中Eden( 伊甸园) 已使用的占当前容量百分比
# O: old代已使用的占当前容量百分比
# P: perm代已使用的占当前容量百分比
# YGC: 从应用程序启动到采样时年轻代中gc次数
# YGCT: 从应用程序启动到采样时年轻代中gc所用时间(s)
# FGC: 从应用程序启动到采样时old代(全gc)gc次数
# FGCT: 从应用程序启动到采样时old代(全gc)gc所用时间(s)
# GCT: 从应用程序启动到采样时gc用的总时间(s)
```
**-gccapacity**:堆内存统计
```shell
[root@localhost ~]# jstat -gccapacity < pid >
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
84480.0 1349632.0 913408.0 54272.0 51200.0 502784.0 168448.0 2699264.0 168448.0 168448.0 21504.0 83968.0 51712.0 51712.0 9 0
# 结果说明
##########################
# NGCMN: 年轻代(young)中初始化(最小)的大小 (kb)
# NGCMX: 年轻代(young)的最大容量 (kb)
# NGC: 年轻代(young)中当前的容量 (kb)
# S0C: 年轻代中第一个survivor( 幸存区) 的容量 (kb)
# S1C: 年轻代中第二个survivor( 幸存区) 的容量 (kb)
# EC: 年轻代中Eden( 伊甸园) 的容量 (kb)
# OGCMN: old代中初始化(最小)的大小 (kb)
# OGCMX: old代的最大容量 (kb)
# OGC: old代当前新生成的容量 (kb)
# OC: Old代的容量 (kb)
# PGCMN: perm代中初始化(最小)的大小 (kb)
# PGCMX: perm代的最大容量 (kb)
# PGC: perm代当前新生成的容量 (kb)
# PC: Perm(持久代)的容量 (kb)
# YGC: 从应用程序启动到采样时年轻代中gc次数
# GCT: 从应用程序启动到采样时gc用的总时间(s)
```
**-gccause**:垃圾收集统计概述(同-gcutil) , 附加最近两次垃圾回收事件的原因
```shell
[root@localhost ~]# jstat -gccause < pid >
S0 S1 E O P YGC YGCT FGC FGCT GCT LGCC GCC
0.00 79.23 39.37 39.92 99.74 9 0.198 0 0.000 0.198 Allocation Failure No GC
# 结果说明
##########################
# LGCC: 最近垃圾回收的原因
# GCC: 当前垃圾回收的原因
```
## jstack
jstack(Java Stack Trace)主要用于打印线程的堆栈信息, 是JDK自带的很强大的线程分析工具, 可以帮助我们排查程序运行时的线程状态、死锁状态等。
```shell
# dump出进程< pid > 的线程堆栈快照至/data/1.log文件中
jstack -l < pid > >/data/1.log
# 参数说明:
# -F: 如果正常执行jstack命令没有响应( 比如进程hung住了) , 可以加上此参数强制执行thread dump
# -m: 除了打印Java的方法调用栈之外, 还会输出native方法的栈帧
# -l: 打印与锁有关的附加信息。使用此参数会导致JVM停止时间变长, 在生产环境需慎用
```
jstack dump文件中值得关注的线程状态有:
- ** 死锁( Deadlock) —— 重点关注**
- 执行中( Runnable)
- ** 等待资源( Waiting on condition) —— 重点关注**
- 等待某个资源或条件发生来唤醒自己。具体需结合jstacktrace来分析, 如线程正在sleep, 网络读写繁忙而等待
- 如果大量线程在“waiting on condition”, 并且在等待网络资源, 可能是网络瓶颈的征兆
- ** 等待获取监视器( Waiting on monitor entry) —— 重点关注**
- 如果大量线程在“waiting for monitor entry”, 可能是一个全局锁阻塞住了大量线程
- 暂停( Suspended)
- 对象等待中( Object.wait() 或 TIMED_WAITING)
- ** 阻塞( Blocked) —— 重点关注**
- 停止( Parked)
**注意**: 如果某个相同的call stack经常出现, 我们有80%的以上的理由确定这个代码存在性能问题(读网络的部分除外)。
**场景一: 分析BLOCKED问题**
```shell
"RMI TCP Connection(267865)-172.16.5.25" daemon prio=10 tid=0x00007fd508371000 nid=0x55ae waiting for monitor entry [0x00007fd4f8684000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.log4j.Category.callAppenders(Category.java:201)
- waiting to lock < 0x00000000acf4d0c0 > (a org.apache.log4j.Logger)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.log(Category.java:853)
at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)
at com.tuan.core.common.lang.cache.remote.SpyMemcachedClient.get(SpyMemcachedClient.java:110)
```
- 线程状态是 Blocked, 阻塞状态。说明线程等待资源超时
- “ waiting to lock < 0x00000000acf4d0c0 > ”指,线程在等待给这个 0x00000000acf4d0c0 地址上锁( 英文可描述为: trying to obtain 0x00000000acf4d0c0 lock)
- 在 dump 日志里查找字符串 0x00000000acf4d0c0, 发现有大量线程都在等待给这个地址上锁。如果能在日志里找到谁获得了这个锁( 如locked < 0x00000000acf4d0c0 > ),就可以顺藤摸瓜了
- “waiting for monitor entry”说明此线程通过 synchronized(obj) {……} 申请进入了临界区, 从而进入了下图1中的“Entry Set”队列, 但该 obj 对应的 monitor 被其他线程拥有,所以本线程在 Entry Set 队列中等待
- 第一行里,"RMI TCP Connection(267865)-172.16.5.25"是 Thread Name 。tid指Java Thread id。nid指native线程的id。prio是线程优先级。[0x00007fd4f8684000]是线程栈起始地址。
**场景二: 分析CPU过高问题**
1.top命令找出最高占用的进程( Shift+P)
2.查看高负载进程下的高负载线程( top -Hp < PID > 或ps -mp < PID > -o THREAD,tid,time)
3.找出最高占用的线程并记录thread_id, 把线程号进行换算成16进制编号( printf "%X\n" thread_id)
4.( 可选) 执行查看高负载的线程名称( jstack 16143 | grep 3fb6)
5.导出进程的堆栈日志, 找到3fb6 这个线程号( jstack 16143 >/home/16143.log)
6.根据找到的堆栈信息关联到代码进行定位分析即可
## jmap
jmap(Java Memory Map)主要用于打印内存映射。常用命令:
`jmap -dump:live,format=b,file=xxx.hprof <pid> `
**查看JVM堆栈的使用情况**
```powershell
[root@localhost ~]# jmap -heap 7243
Attaching to process ID 27900, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 20.45-b01
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration: #堆内存初始化配置
MinHeapFreeRatio = 40 #-XX:MinHeapFreeRatio设置JVM堆最小空闲比率
MaxHeapFreeRatio = 70 #-XX:MaxHeapFreeRatio设置JVM堆最大空闲比率
MaxHeapSize = 100663296 (96.0MB) #-XX:MaxHeapSize=设置JVM堆的最大大小
NewSize = 1048576 (1.0MB) #-XX:NewSize=设置JVM堆的‘ 新生代’ 的默认大小
MaxNewSize = 4294901760 (4095.9375MB) #-XX:MaxNewSize=设置JVM堆的‘ 新生代’ 的最大大小
OldSize = 4194304 (4.0MB) #-XX:OldSize=设置JVM堆的‘ 老生代’ 的大小
NewRatio = 2 #-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8 #-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值
PermSize = 12582912 (12.0MB) #-XX:PermSize=< value > :设置JVM堆的‘ 持久代’ 的初始大小
MaxPermSize = 67108864 (64.0MB) #-XX:MaxPermSize=< value > :设置JVM堆的‘ 持久代’ 的最大大小
Heap Usage:
New Generation (Eden + 1 Survivor Space): #新生代区内存分布,包含伊甸园区+1个Survivor区
capacity = 30212096 (28.8125MB)
used = 27103784 (25.848182678222656MB)
free = 3108312 (2.9643173217773438MB)
89.71169693092462% used
Eden Space: #Eden 区内存分布
capacity = 26869760 (25.625MB)
used = 26869760 (25.625MB)
free = 0 (0.0MB)
100.0% used
From Space: #其中一个Survivor区的内存分布
capacity = 3342336 (3.1875MB)
used = 234024 (0.22318267822265625MB)
free = 3108312 (2.9643173217773438MB)
7.001809512867647% used
To Space: #另一个Survivor区的内存分布
capacity = 3342336 (3.1875MB)
used = 0 (0.0MB)
free = 3342336 (3.1875MB)
0.0% used
PS Old Generation: #当前的Old区内存分布
capacity = 67108864 (64.0MB)
used = 67108816 (63.99995422363281MB)
free = 48 (4.57763671875E-5MB)
99.99992847442627% used
PS Perm Generation: #当前的 “持久代” 内存分布
capacity = 14417920 (13.75MB)
used = 14339216 (13.674942016601562MB)
free = 78704 (0.0750579833984375MB)
99.45412375710227% used
```
新生代内存回收就是采用空间换时间方式; 如果from区使用率一直是100% 说明程序创建大量的短生命周期的实例, 使用jstat统计jvm在内存回收中发生的频率耗时以及是否有full gc, 使用这个数据来评估一内存配置参数、gc参数是否合理。
**统计一【jmap -histo】**:统计所有类的实例数量和所占用的内存容量
```powershell
[root@localhost ~]# jmap -histo 7243
num #instances #bytes class name
----------------------------------------------
1: 8969 19781168 [B
2: 1835 2296720 [I
3: 19735 2050688 [C
4: 3448 385608 java.lang.Class
5: 3829 371456 [Ljava.lang.Object;
6: 14634 351216 java.lang.String
7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node
8: 6257 100112 java.lang.Object
9: 2155 68960 java.util.HashMap$Node
10: 723 63624 java.lang.reflect.Method
11: 49 56368 [Ljava.util.concurrent.ConcurrentHashMap$Node;
12: 830 46480 java.util.zip.ZipFile$ZipFileInputStream
13: 1146 45840 java.lang.ref.Finalizer
......
```
**统计二【jmap -histo】**: 查看对象数最多的对象, 并过滤Map关键词, 然后按降序排序输出
```shell
[root@localhost ~]# jmap -histo 7243 |grep Map|sort -k 2 -g -r|less
Total 96237 26875560
7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node
9: 2155 68960 java.util.HashMap$Node
18: 563 27024 java.util.HashMap
21: 505 20200 java.util.LinkedHashMap$Entry
16: 337 34880 [Ljava.util.HashMap$Node;
27: 336 16128 gnu.trove.THashMap
56: 163 6520 java.util.WeakHashMap$Entry
60: 127 6096 java.util.WeakHashMap
38: 127 10144 [Ljava.util.WeakHashMap$Entry;
53: 126 7056 java.util.LinkedHashMap
......
```
**统计三【jmap -histo】**: 统计实例数量最多的前10个类
```shell
[root@localhost ~]# jmap -histo 7243 | sort -n -r -k 2 | head -10
num #instances #bytes class name
----------------------------------------------
Total 96237 26875560
3: 19735 2050688 [C
6: 14634 351216 java.lang.String
1: 8969 19781168 [B
7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node
8: 6257 100112 java.lang.Object
5: 3829 371456 [Ljava.lang.Object;
4: 3448 385608 java.lang.Class
9: 2155 68960 java.util.HashMap$Node
2: 1835 2296720 [I
```
**统计四【jmap -histo】**: 统计合计容量最多的前10个类
```shell
[root@localhost ~]# jmap -histo 7243 | sort -n -r -k 3 | head -10
num #instances #bytes class name
----------------------------------------------
Total 96237 26875560
1: 8969 19781168 [B
2: 1835 2296720 [I
3: 19735 2050688 [C
4: 3448 385608 java.lang.Class
5: 3829 371456 [Ljava.lang.Object;
6: 14634 351216 java.lang.String
7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node
8: 6257 100112 java.lang.Object
9: 2155 68960 java.util.HashMap$Node
```
**dump注意事项**
- 在应用快要发生FGC的时候把堆数据导出来
老年代或新生代used接近100%时, 就表示即将发生GC, 也可以再JVM参数中指定触发GC的阈值。
- 查看快要发生FGC使用命令: jmap -heap < pid >
- 数据导出: jmap -dump:format=b,file=heap.bin < pid >
- 通过命令查看大对象: jmap -histo < pid > |less
**使用总结**
- 如果程序内存不足或者频繁GC, 很有可能存在内存泄露情况, 这时候就要借助Java堆Dump查看对象的情况
- 要制作堆Dump可以直接使用jvm自带的jmap命令
- 可以先使用`jmap -heap`命令查看堆的使用情况,看一下各个堆空间的占用情况
- 使用`jmap -histo:[live]`查看堆内存中的对象的情况。如果有大量对象在持续被引用,并没有被释放掉,那就产生了内存泄露,就要结合代码,把不用的对象释放掉
- 也可以使用 `jmap -dump:format=b,file=<fileName>` 命令将堆信息保存到一个文件中, 再借助jhat命令查看详细内容
- 在内存出现泄露、溢出或者其它前提条件下, 建议多dump几次内存, 把内存文件进行编号归档, 便于后续内存整理分析
- 在用cms gc的情况下, 执行jmap -heap有些时候会导致进程变T, 因此强烈建议别执行这个命令, 如果想获取内存目前每个区域的使用状况, 可通过jstat -gc或jstat -gccapacity来拿到
## jhat
jhat( JVM Heap Analysis Tool) 命令是与jmap搭配使用, 用来分析jmap生成的dump, jhat内置了一个微型的HTTP/HTML服务器, 生成dump的分析结果后, 可以在浏览器中查看。在此要注意, 一般不会直接在服务器上进行分析, 因为jhat是一个耗时并且耗费硬件资源的过程, 一般把服务器生成的dump文件复制到本地或其他机器上进行分析。
```powershell
# 解析Java堆转储文件,并启动一个 web server
jhat heapDump.dump
```
## jconsole
jconsole(Java Monitoring and Management Console)是一个Java GUI监视工具, 可以以图表化的形式显示各种数据, 并可通过远程连接监视远程的服务器VM。用java写的GUI程序, 用来监控VM, 并可监控远程的VM, 非常易用, 而且功能非常强。命令行里打jconsole, 选则进程就可以了。
**第一步**: 在远程机的tomcat的catalina.sh中加入配置:
```powershell
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.202.121 -Dcom.sun.management.jmxremote"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=12345"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.pwd.file=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el7_2.x86_64/jre/lib/management/jmxremote.password"
```
**第二步**:配置权限文件
```powershell
[root@localhost bin]# cd /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el7_2.x86_64/jre/lib/management/
[root@localhost management]# cp jmxremote.password.template jmxremote.password
[root@localhost management]# vi jmxremote.password
```
monitorRole QED
controlRole chenqimiao
**第三步**: 配置权限文件为600
```powershell
[root@localhost management]# chmod 600 jmxremote.password jmxremote.access
```
这样基本配置就结束了, 下面说两个坑, 第一个就是防火墙的问题, 要开放指定端口的防火墙, 我这里配置的是12345端口, 第二个是hostname的问题:

请将127.0.0.1修改为本地真实的IP,我的服务器IP是192.168.202.121:

** 第四步**: 查看JConsole


## jvisualvm
jvisualvm(JVM Monitoring/Troubleshooting/Profiling Tool)同jconsole都是一个基于图形化界面的、可以查看本地及远程的JAVA GUI监控工具, Jvisualvm同jconsole的使用方式一样, 直接在命令行打入Jvisualvm即可启动, 不过Jvisualvm相比, 界面更美观一些, 数据更实时。 jvisualvm的使用VisualVM进行远程连接的配置和JConsole是一摸一样的, 最终效果图如下

**Visual GC(监控垃圾回收器)**
Java VisualVM默认没有安装Visual GC插件, 需要手动安装, JDK的安装目录的bin目露下双击 jvisualvm.sh, 即可打开Java VisualVM, 点击菜单栏: ** 工具->插件** 安装Visual GC, 最终效果如下图所示:

**大dump文件**
从服务器dump堆内存后文件比较大( 5.5G左右) , 加载文件、查看实例对象都很慢, 还提示配置xmx大小。表明给VisualVM分配的堆内存不够, 找到$JAVA_HOME/lib/visualvm}/etc/visualvm.conf这个文件, 修改:
```shell
default_options="-J-Xms24m -J-Xmx192m"
```
再重启VisualVM就行了。
## jmc
jmc( Java Mission Control) 是JDK自带的一个图形界面监控工具, 监控信息非常全面。JMC打开性能日志后, 主要包括**一般信息、内存、代码、线程、I/O、系统、事件** 功能。

JMC的最主要的特征就是JFR( Java Flight Recorder) , 是基于JAVA的飞行记录器, JFR的数据是一些列JVM事件的历史纪录, 可以用来诊断JVM的性能和操作, 收集后的数据可以使用JMC来分析。
### 启动JFR
在商业版本上面, JFR默认是关闭的, 可以通过在启动时增加参数 `-XX:+UnlockCommercialFeatures -XX:+FlightRecorder` 来启动应用。启动之后, 也只是开启了JFR特性, 但是还没有开始进行事件记录。这就要通过GUI和命令行了。
- ** 通过Java Mission Control启动JFR**
打开Java Mission Control点击对应的JVM启动即可, 事件记录有两种模式( 如果选择第2种模式, 那么JVM会使用一个循环使用的缓存来存放事件数据) :
- 记录固定一段时间的事件( 比如: 1分钟)
- 持续进行记录
- ** 通过命令行启动JFR**
通过在启动的时候,增加参数:`-XX:+FlightRecorderOptions=string` 来启动真正地事件记录,这里的 `string` 可以是以下值( 下列参数都可以使用jcmd命令, 在JVM运行的时候进行动态调整, [参考地址](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html)) :
- `name=name` : 标识recording的名字( 一个进程可以有多个recording存在, 它们使用名字来进行区分)
- `defaultrecording=<ture|false>` : 是否启动recording, 默认是false, 我们要分析, 必须要设置为true
- `setting=paths` : 包含JFR配置的文件名字
- `delay=time` : 启动之后, 经过多长时间( 比如: 30s, 1h) 开始进行recording
- `duration=time` : 做多长时间的recording
- `filename=path` : recordding记录到那个文件里面
- `compress=<ture|false>` : 是否对recording进行压缩( gzip) ,默认为false
- `maxage=time` :在循环使用的缓存中,事件数据保存的最大时长
- `maxsize=size` : 事件数据缓存的最大大小( 比如: 1024k, 1M)
### 常用JFR命令
- 启动recording
命令格式:`jcmd process_id JFR.start [options_list]`, 其中options_list就是上述的参数值。
- dump出循环缓存中的数据
命令格式:`jcmd process_id JFR.dump [options_list]`, 其中options_list参数的可选值如下:
- `name=name` : recording的名字
- `recording=n` : JFR recording的数字( 一个标识recording的随机数)
- `filename=path` : dump文件的保存路径
- 查看进程中所有recording
命令格式:` jcmd process_id JFR.check [verbose]`, 不同recording使用名字进行区分, 同时JVM还为它分配一个随机数。
- 停止recording
命令格式:` jcmd process_id JFR.stop [options_list]`, 其中options_list参数的可选值如下:
- `name=name` : 要停止的recording名字
- `recording=n` : 要停止的recording的标识数字
- `discard=boolean` : 如果为true, 数据被丢弃, 而不是写入下面指定的文件当中
- `filename=path` :写入数据的文件名称
### 命令启动JFR案例
- ** 第一步**: 创建一个包含了你自己配置的JFR模板文件( `template.jfc`) 。运行jmc, 然后Window->Flight Recording Template Manage菜单。准备好档案后, 就可以导出文件, 并移动到要排查问题的环境中

- ** 第二步**: 由于JFR需要JDK的商业证书, 这一步需要解锁jdk的商业特性
```powershell
[root@localhost bin]# jcmd 12234 VM.unlock_commercial_features
12234: Commercial Features already unlocked.
```
- ** 第三步**: 最后你就可以启动JFR, 命令格式如下:
```powershell
jcmd < PID > JFR.start name=test duration=60s [settings=template.jfc] filename=output.jfr
```
上述命令立即启动JFR并开始使用 `template.jfc` (在 `$JAVA_HOME/jre/lib/jfr` 下有 `default.jfc` 和 `profile.jfc` 模板)的配置收集 `60s` 的JVM信息, 输出到 `output.jfr` 中。一旦记录完成之后,就可以复制.jfr文件到你的工作环境使用jmc GUI来分析。它几乎包含了排查jvm问题需要的所有信息, 包括堆dump时的异常信息。使用案例如下:
```powershell
[root@localhost bin]# jcmd 12234 JFR.start name=test duration=60s filename=output.jfr
12234: Started recording 6. The result will be written to: /root/zookeeper-3.4.12/bin/output.jfr
[root@localhost bin]# ls -l
-rw-r--r-- 1 root root 298585 6月 29 11:09 output.jfr
```
**JFR( Java Flight Recorder) **
- Java Mission Control的最主要的特征就是Java Flight Recorder。正如它的名字所示, JFR的数据是一些列JVM事件的历史纪录, 可以用来诊断JVM的性能和操作
- JFR的基本操作就是开启哪些事件( 比如: 线程由于等待锁而阻塞的事件) 。当开启的事件发生了, 事件相关的数据会记录到内存或磁盘文件上。记录事件数据的缓存是循环使用的, 只有最近发生的事件才能够从缓存中找到, 之前的都因为缓存的限制被删除了。Java Mission Control能够对这些事件在界面上进行展示( 从JVM的内存中读取或从事件数据文件中读取) , 我们可以通过这些事件来对JVM的性能进行诊断
- 事件的类型、缓存的大小、事件数据的存储方式等等都是通过JVM参数、Java Mission Control的GUI界面、jcmd命令来控制的。JFR默认是编译进程序的, 因为它的开销很小, 一般来说对应用的影响小于1%。不过, 如果我们增加了事件的数目、修改了记录事件的阈值, 都有可能增加JFR的开销
### JFR概况
下面对GlassFish web服务器进行JFR记录的例子, 在这个服务器上面运行着在第2章介绍的股票servlet。Java Mission Control加载完JFR获取的事件之后, 大概是下面这个样子:

我们可以看到, 通过上图可以看到: CPU使用率, Heap使用率, JVM信息, System Properties, JFR的记录情况等等。
### JFR内存视图
Java Mission Control 可以看到非常多的信息, 下图只显示了一个标签的内容。下图显示了JVM 的内存波动非常频繁, 因为新生代经常被清除( 有意思的是, head的大小并没有增长) 。下面左边的面板显示了最近一段时间的垃圾回收情况, 包括: GC的时长和垃圾回收的类型。如果我们点击一个事件, 右边的面板会展示这个事件的具体情况, 包括: 垃圾垃圾回收的各个阶段及其统计信息。从面板的标签可以看到, 还有很多其它信息, 比如: 有多少对象被清除了, 花了多长时间; GC算法的配置; 分配的对象信息等等。在第5章和第6章中, 我们会详细介绍。

### JFR 代码视图
这张图也有很多tab, 可以看到各个包的使用频率和类的使用情况、异常、编译、代码缓存、类加载情况等等:

### JFR事件视图
下图显示了事件的概述视图:

## EclipseMAT
虽然Java虚拟机可以帮我们对内存进行回收, 但是其回收的是Java虚拟机不再引用的对象。很多时候我们使用系统的IO流、Cursor、Receiver如果不及时释放, 就会导致内存泄漏( OOM) 。但是, 很多时候内存泄漏的现象不是很明显, 比如内部类、Handler相关的使用导致的内存泄漏, 或者你使用了第三方library的一些引用, 比较消耗资源, 但又不是像系统资源那样会引起你足够的注意去手动释放它们。以下通过内存泄漏分析、集合使用率、Hash性能分析和OQL快读定位空集合来使用MAT。
**GC Roots**
JAVA虚拟机通过可达性( Reachability)来判断对象是否存活,基本思想:`以”GC Roots”的对象作为起始点向下搜索, 搜索形成的路径称为引用链, 当一个对象到GC Roots没有任何引用链相连( 即不可达的) , 则该对象被判定为可以被回收的对象, 反之不能被回收`。GC Roots可以是以下任意对象
- 一个在current thread( 当前线程) 的call stack( 调用栈) 上的对象( 如方法参数和局部变量)
- 线程自身或者system class loader( 系统类加载器) 加载的类
- native code( 本地代码) 保留的活动对象
**内存泄漏**
当对象无用了,但仍然可达(未释放),垃圾回收器无法回收。
**Java四种引用类型**
- Strong References( 强引用)
普通的java引用, 我们通常new的对象就是: `StringBuffer buffer = new StringBuffer();` 如果一个对象通过一串强引用链可达,那么它就不会被垃圾回收。你肯定不希望自己正在使用的引用被垃圾回收器回收吧。但对于集合中的对象,应在不使用的时候移除掉,否则会占用更多的内存,导致内存泄漏。
- Soft Reference( 软引用)
当对象是Soft Reference可达时, gc会向系统申请更多内存, 而不是直接回收它, 当内存不足的时候才回收它。因此Soft Reference适合用于构建一些缓存系统, 比如图片缓存。
- Weak Reference( 弱引用)
WeakReference不会强制对象保存在内存中。它拥有比较短暂的生命周期, 允许你使用垃圾回收器的能力去权衡一个对象的可达性。在垃圾回收器扫描它所管辖的内存区域过程中, 一旦gc发现对象是Weak Reference可达, 就会把它放到 `Reference Queue` 中, 等下次gc时回收它。
系统为我们提供了WeakHashMap, 和HashMap类似, 只是其key使用了weak reference。如果WeakHashMap的某个key被垃圾回收器回收, 那么entity也会自动被remove。由于WeakReference被GC回收的可能性较大, 因此, 在使用它之前, 你需要通过weakObj.get()去判断目的对象引用是否已经被回收。一旦WeakReference.get()返回null, 它指向的对象就会被垃圾回收, 那么WeakReference对象就没有用了, 意味着你应该进行一些清理。比如在WeakHashMap中要把回收过的key从Map中删除掉, 避免无用的的weakReference不断增长。
ReferenceQueue可以让你很容易地跟踪dead references。WeakReference类的构造函数有一个ReferenceQueue参数, 当指向的对象被垃圾回收时, 会把WeakReference对象放到ReferenceQueue中。这样, 遍历ReferenceQueue可以得到所有回收过的WeakReference。
- Phantom Reference( 虚引用)
其余Soft/Weak Reference区别较大是它的get()方法总是返回null。这意味着你只能用Phantom Reference本身, 而得不到它指向的对象。当Weak Reference指向的对象变得弱可达(weakly reachable) 时会立即被放到ReferenceQueue中, 这在finalization、garbage collection之前发生。理论上, 你可以在finalize()方法中使对象“复活”( 使一个强引用指向它就行了, gc不会回收它) 。但没法复活PhantomReference指向的对象。而PhantomReference是在garbage collection之后被放到ReferenceQueue中的, 没法复活。
**MAT视图与概念**
- **Shallow Heap**
Shallow Size就是对象本身占用内存的大小, 不包含其引用的对象内存, 实际分析中作用不大。 常规对象( 非数组) 的Shallow Size由其成员变量的数量和类型决定。数组的Shallow Size有数组元素的类型( 对象类型、基本类型) 和数组长度决定。案例如下:
```java
public class String {
public final class String {8 Bytes header
private char value[]; 4 Bytes
private int offset; 4 Bytes
private int count; 4 Bytes
private int hash = 0; 4 Bytes
// ......
}
// "Shallow size“ of a String ==24 Bytes12345678
```
Java的对象成员都是些引用。真正的内存都在堆上, 看起来是一堆原生的byte[]、char[]、int[], 对象本身的内存都很小。所以我们可以看到以Shallow Heap进行排序的Histogram图中, 排在第一位第二位的是byte和char。
- **Retained Heap**
Retained Heap值的计算方式是将Retained Set中的所有对象大小叠加。或者说, 由于X被释放, 导致其它所有被释放对象( 包括被递归释放的) 所占的Heap大小。当X被回收时哪些将被GC回收的对象集合。比如:
一个ArrayList持有100000个对象, 每一个占用16 bytes, 移除这些ArrayList可以释放16× 100000+X, X代表ArrayList的Shallow大小。相对于Shallow Heap, Retained Heap可以更精确的反映一个对象实际占用的大小( 因为如果该对象释放, Retained Heap都可以被释放) 。
- **Histogram**
可列出每一个类的实例数。支持正则表达式查找, 也可以计算出该类所有对象的Retained Size。

- **Dominator Tree**
对象之间dominator关系树。如果从GC Root到达Y的的所有path都经过X, 那么我们称X dominates Y, 或者X是Y的Dominator Dominator Tree由系统中复杂的对象图计算而来。从MAT的dominator tree中可以看到占用内存最大的对象以及每个对象的dominator。 我们也可以右键选择Immediate Dominator”来查看某个对象的dominator。

- **Path to GC Roots**
查看一个对象到RC Roots的引用链通常在排查内存泄漏的时候, 我们会选择exclude all phantom/weak/soft etc.references,
意思是查看排除虚引用/弱引用/软引用等的引用链,因为被虚引用/弱引用/软引用的对象可以直接被GC给回收, 我们要看的就是某个对象否还存在Strong 引用链( 在导出HeapDump之前要手动出发GC来保证) , 如果有, 则说明存在内存泄漏, 然后再去排查具体引用。

查看当前Object所有引用,被引用的对象:
- List objects with ( 以Dominator Tree的方式查看)
- incoming references 引用到该对象的对象
- outcoming references 被该对象引用的对象
- Show objects by class ( 以class的方式查看)
- incoming references 引用到该对象的对象
- outcoming references 被该对象引用的对象
- **OQL(Object Query Language)**
类似SQL查询语言: Classes: Table、Objects: Rows、Fileds: Cols
```mysql
select * from com.example.mat.Listener
# 查找size= 0并且未使用过的ArrayList
select * from java.util.ArrayList where size=0 and modCount=01
# 查找所有的Activity
select * from instanceof android.app.Activity
```
- ** 内存快照对比**
方式一: Compare To Another Heap Dump( 直接进行比较)



方式二: Compare Baseket( 更全面, 可以直接给出百分比)




**MAT内存分析实战**
- ** 实战一:内存泄漏分析**
查找导致内存泄漏的类。既然环境已经搭好, heap dump也成功倒入, 接下来就去分析问题。
- 查找目标类
如果在开发过程中, 你的目标很明确, 比如就是查找自己负责的服务, 那么通过包名或者Class筛选, OQL搜索都可以快速定位到。点击OQL图标, 在窗口输入, 并按Ctrl + F5或者!按钮执行:
`select * from instanceof android.app.Activity`
- Paths to GC Roots: exclude all phantom/weak/soft etc.references
查看一个对象到RC Roots是否存在引用链。要将虚引用/弱引用/软引用等排除,因为被虚引用/弱引用/软引用的对象可以直接被GC给回收
- 分析具体的引用为何没有被释放,并进行修复
**小技巧:**
- 当目的不明确时, 可以直接定位到RetainedHeap最大的Object, Select incoming references, 查看引用链, 定位到可疑的对象然后Path to GC Roots进行引用链分析
- 如果大对象筛选看不出区别, 可以试试按照class分组, 再寻找可疑对象进行GC引用链分析
- 直接按照包名直接查看GC引用链, 可以一次性筛选多个类, 但是如下图所示, 选项是 Merge Shortest Path to GCRoots, 这个选项具体不是很明白, 不过也能筛选出存在GC引用链的类, 这种方式的准确性还待验证

所以有时候进行MAT分析还是需要一些经验, 能够帮你更快更准确的定位。
- ** 实战二:集合使用率分析**
集合在开发中会经常使用到, 如何选择合适的数据结构的集合, 初始容量是多少( 太小, 可能导致频繁扩容) , 太大, 又会开销跟多内存。当这些问题不是很明确时或者想查看集合的使用情况时, 可以通过MAT来进行分析。
- ** 筛选目标对象**

- **Show Retained Set( 查找当X被回收时那些将被GC回收的对象集合) **

- ** 筛选指定的Object( Hash Map, ArrayList) 并按照大小进行分组**

- ** 查看指定类的Immediate dominators**

**Collections fill ratio**
这种方式只能查看那些具有预分配内存能力的集合, 比如HashMap, ArrayList。计算方式: ”size / capacity”


- ** 实战三: Hash相关性能分析**
当Hash集合中过多的对象返回相同Hash值的时候, 会严重影响性能( Hash算法原理自行搜索) , 这里来查找导致Hash集合的碰撞率较高的罪魁祸首。
- **Map Collision Ratio**
检测每一个HashMap或者HashTable实例并按照碰撞率排序: **碰撞率 = 碰撞的实体/Hash表中所有实体**

- ** 查看Immediate dominators**


- ** 通过HashEntries查看key value**

- **Array等其它集合分析方法类似**
- ** 实战四: 通过OQL快速定位未使用的集合**
- ** 通过OQL查询empty并且未修改过的集合: **
```mysql
select * from java.util.ArrayList where size=0 and modCount=01
select * from java.util.HashMap where size=0 and modCount=0
select * from java.util.Hashtable where count=0 and modCount=012
```

- **Immediate dominators(查看引用者)**

- ** 计算空集合的Retained Size值, 查看浪费了多少内存**

## 🔥火焰图
火焰图是用来分析程序运行瓶颈的工具。火焰图也可以用来分析 Java 应用。
### 环境安装
确认你的机器已经安装了**git、jdk、perl、c++编译器**。
#### 安装Perl
```shell
wget http://www.cpan.org/src/5.0/perl-5.26.1.tar.gz
tar zxvf perl-5.26.1.tar.gz
cd perl-5.26.1
./Configure -de
make
make test
make install
```
wget后面的路径可以按需更改。安装过程比较耗时间, 安装完成后可通过**perl -version**查看是否安装成功。
#### C++编译器
```shell
apt-get install g++
```
一般用于编译c++程序, 缺少这个编译器进行make编译c++代码时, 会报“g++: not found”的错误。
#### clone相关项目
下载下来所需要的两个项目( 这里建议放到data目录下) :
```shell
git clone https://github.com/jvm-profiling-tools/async-profiler
git clone https://github.com/brendangregg/FlameGraph
```
#### 编译项目
下载好以后, 需要打开async-profiler文件, 输入make指令进行编译:
```shell
cd async-profiler
make
```
### 生成文件
#### 生成火焰图数据
可以从 github 上下载 [async-profiler ](https://github.com/jvm-profiling-tools/async-profiler ) 的压缩包进行相关操作。进入async-profiler项目的目录下, 然后输入如下指令:
```shell
./profiler.sh -d 60 -o collapsed -f /tmp/test_01.txt ${pid}
```
上面的-d表示的是持续时长, 后面60代表持续采集时间60s, -o表示的是采集规范, 这里用的是collapsed, -f后面的路径, 表示的是数据采集后生成的数据存放的文件路径( 这里放在了/tmp/test_01.txt) , ${pid}表示的是采集目标进程的pid, 回车运行, 运行期间阻塞, 知道约定时间完成。运行完成后去tmp下看看有没有对应文件。
#### 生成svg文件
上一步产生的文件里的内容,肉眼是很难看懂的,所以现在[FlameGraph](https://github.com/brendangregg/FlameGraph)的作用就体现出来了,它可以读取该文件并生成直观的火焰图,现在进入该项目目录下面,执行如下语句:
```shell
perl flamegraph.pl --colors=java /tmp/test_01.txt > test_01.svg
```
因为是perl文件, 这里使用perl指令运行该文件, 后面--colors表示着色风格, 这里是java, 后面的是数据文件的路径, 这里是刚刚上面生成的那个文件/tmp/test_01.txt, 最后的test_01.svg就是最终生成的火焰图文件存放的路径和文件命名, 这里是命名为test_01.svg并保存在当前路径, 运行后看到该文件已经存在于当前目录下。
#### 展示火焰图
现在下载下来该文件,使用浏览器打开,效果如下:

### 火焰图案例
生成的[火焰图案例](images/DevOps/火焰图案例.svg)如下:

#### 瓶颈点1
CoohuaAnalytics$KafkaConsumer:::send方法中Gzip压缩占比较高。已经定位到方法级别, 再看代码就快速很多, 直接找到具体位置, 找到第一个消耗大户: **Gzip压缩**

#### 瓶颈点2
展开2这个波峰, 查看到这个getOurStackTrace方法占用了大比例的CPU, 怀疑代码里面频繁用丢异常的方式获取当前代码栈:

直接看代码:

果然如推断, 找到第二个CPU消耗大户: new Exception().getStackTrace()。
#### 瓶颈点3
展开波峰3, 可以看到是这个Gzip解压缩:

定位到具体的代码, 可以看到对每个请求的参数进行了gzip解压缩:

# Linux Command