JVM监控及诊断工具-GUI
实际中,你下面有1-2款会用即可
一、工具概述
二、JConsole
了解
1、基本概述
2、启动
- 在jdk安装目录中找到jconsole.exe,双击该可执行文件就可以
- 打开DOS窗口,直接输入jconsole就可以了
3、三种连接方式
①Local
使用JConsole连接一个正在本地系统运行的JVM,并且执行程序的和运行JConsole的需要是同一个用户。JConsole使用文件系统的授权通过RMI连接起链接到平台的MBean的服务器上。这种从本地连接的监控能力只有Sun的JDK具有。
注意:本地连接要求 启动jconsole的用户 和 运行当前程序的用户 是同一个用户
具体操作如下:
1、在DOS窗口中输入jconsole
2、在控制台上填写相关信息
3、选择“不安全的连接”
4、进入控制台页面
②、Remote
使用下面的URL通过RMI连接器连接到一个JMX代理,service:jmx:rmi:///jndi/rmi://hostName:portNum/jmxrmi。JConsole为建立连接,需要在环境变量中设置mx.remote.credentials来指定用户名和密码,从而进行授权。
③Advanced
使用一个特殊的URL连接JMX代理。一般情况使用自己定制的连接器而不是RMI提供的连接器来连接JMX代理,或者是一个使用JDK1.4的实现了JMX和JMX Rmote的应用
4、主要作用
1、概览
2、内存
3、根据线程检测死锁
4、线程
5、VM 概要
三、Visual VM
掌握
jvisualvm和visual vm的区别:
visual vm是单独下载的工具,然后将visual vm结合到jdk中就变成了jvisualvm,仅仅是添加了一个j而已,这个j应该是java的用处,所以说jvisualvm其实就是visual vm
1、基本概述
使用:
1、在jdk安装目录中找到jvisualvm.exe,然后双击执行即可
2、打开DOS窗口,输入jvisualvm就可以打开该软件
2、插件的安装
首先在IDEA中搜索VisualVM Launcher插件
并安装:
2、重启IDEA,然后配置该插件
3、使用两种方式来运行程序
4、运行效果
还是打开jvisualvm界面,只是不需要我们手动打开jvisualvm而已
3、连接方式
4、主要功能
①生成/读取堆内存快照
一、生成堆内存快照
1、方式1:
2、方式2:
注意:
生成堆内存快照如下图:
这些快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:
二、装入堆内存快照
②查看JVM参数和系统属性
③查看运行中的虚拟机进程
④生成/读取线程快照
一、生成线程快照
1、方式1:
2、方式2:
注意:
生成线程快照如下图:
这些快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:
二、装入线程快照
⑤程序资源的实时监控
⑥其他功能
四、Eclipse MAT
主要分析堆内存dump文件,找出内存泄露
问题
1、基本概述
注意:如果单独使用,那么解压即可用,不需要安装即可
2、获取堆dump文件
①dump文件内存
②两点说明
③获取dump文件
3、分析堆dump文件
①histogram
展示了各个类的实例数目以及这些实例的Shallow heap或者Retained heap的总和
图标:
具体内容:
②thread overview
图标:
具体信息:
③获得对象互相引用的关系
- with outgoing references
变量被外部的引用链
图示:
结果:
- with incoming references
这个变量被谁引用了
图示:
结果:
④浅堆与深堆
- shallow heap,浅堆
不算引用类型的大小,就是只算基本数据类型大小
对象头代表根据类创建的对象的对象头,还有对象的大小不是可能向8字节对齐,而是就向8字节对齐
- retained heap,深堆
注意:
当前深堆大小 = 当前对象的浅堆大小 + 对象中所包含对象的深堆大小
- 补充:对象实际大小
- A的浅堆大小是A他自己
- 浅堆不包含引用空间
- A的深堆大小是A+D
- 深堆大小包含浅堆大小 加上 只有被A所指向的引用对象大小(就是上面的D,因为C被A/B同时指向),所以不算
- A的实际对象大小是ACD
- 实际对象大小是包含A本身,且他能可达触及到的所有对象大小,A本身+指向的C和D
- 练习
A
- 浅堆:A
- 深堆:A
- 对象实际大小:A
B
- 浅堆:B
- 深堆:B C
- 对象实际大小: B C D
- 案例分析:StudentTrace
代码:
/**
* 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。
* 它由三个部分组成:Student、WebPage和StudentTrace三个类
*
* -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=c:\code\student.hprof
*/
public class StudentTrace {
static List webpages = new ArrayList();
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 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 getHistory() {
return history;
}
public void setHistory(List 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个字节
解释:
我说“elementData数组的浅堆是80个字节”,其中15个对象一共是60个字节,对象头8个字节,数组对象本身4个字节,这些的和是72个字节,然后总和要是8的倍数,所以“elementData数组的浅堆是80个字节”
我说“WebPage对象的深堆之和是1208个字节”,一共有15个对象,其中0、21、42、63、84、35、70不仅仅是7的倍数,还是3或者5的倍数,所以这几个数值对应的i不能计算在深堆之内,这15个对象中大多数的深堆是152个字节,但是i是0和7的那两个深堆是144个字节,所以(13152+1442)-(6*152+144)=1208,所以这也印证了我上面的话,即“WebPage对象的深堆之和是1208个字节”
因此“elementData数组的浅堆80个字节”加上“WebPage对象的深堆之和1208个字节”,正好是1288个字节,说明“elementData数组的浅堆1288个字节”
⑤支配树
注意:
跟随我一起来理解如何从“对象引用图—》支配树”,首先需要理解支配者(如果要到达对象B,毕竟经过对象A,那么对象A就是对象B的支配者,可以想到支配者大于等于1),然后需要理解直接支配者(在支配者中距离对象B最近的对象A就是对象B的直接支配者,你要明白直接支配者不一定就是对象B的上一级,然后直接支配者只有一个),然后还需要理解支配树是怎么画的,其实支配树中的对象与对象之间的关系就是直接支配关系,也就是上一级是下一级的直接支配者,只要按照这样的方式来作图,肯定能从“对象引用图—》支配树”
在Eclipse MAT工具中如何查看支配树:
↑的Student的支配树引用就先8个,如果回收了上面的Student,那么他支配的8个对象都会被回收
4、案例:Tomcat堆溢出分析
①说明
②分析过程
5、支持使用OQL语言查询对象信息
五、再谈内存泄露
1、内存泄露的理解与分析
对象已经对我们没用了,我们需要回收他;但是有别的我们需要使用的对象引用他,就无法回收这些我们认为他是垃圾,但是他不被jvm认为是垃圾
2、Java中内存泄露的8种情况
①静态集合类
②单例模式
③内部类持有外部类
④各种连接,如数据库连接、网络连接和IO连接等
⑤变量不合理的作用域
⑥改变哈希值
例1:
/**
* 演示内存泄漏
*
* @author shkstart
* @create 14:43
*/
public class ChangeHashCode {
public static void main(String[] args) {
HashSet set = new HashSet();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
p1.name = "CC";//导致了内存的泄漏
set.remove(p1); //删除失败
System.out.println(set);
set.add(new Person(1001, "CC"));
System.out.println(set);
set.add(new Person(1001, "AA"));
System.out.println(set);
}
}
class Person {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
if (id != person.id) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
例2:
/**
* 演示内存泄漏
* @author shkstart
* @create 14:47
*/
public class ChangeHashCode1 {
public static void main(String[] args) {
HashSet hs = new HashSet();
Point cc = new Point();
cc.setX(10);//hashCode = 41
hs.add(cc);
//因此会计算出新的hash值,进行接下来的操作
cc.setX(20);//修改了计算hash的成员值。hashCode = 51 此行为导致了内存的泄漏
System.out.println("hs.remove = " + hs.remove(cc));//false
hs.add(cc);
System.out.println("hs.size = " + hs.size());//size = 2
System.out.println(hs);
}
}
class Point {
int x;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Point other = (Point) obj;
if (x != other.x) return false;
return true;
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
'}';
}
}
⑦缓存泄露
对缓存的对象使用弱引用map等。。。WeakHashMap
弱引用,GC中一旦发现,就回收
例子:
/**
* 演示内存泄漏
*
* @author shkstart
* @create 14:53
*/
public class MapTest {
static Map wMap = new WeakHashMap();
static Map map = new HashMap();
public static void main(String[] args) {
init();
testWeakHashMap();
testHashMap();
}
public static void init() {
String ref1 = new String("obejct1");
String ref2 = new String("obejct2");
String ref3 = new String("obejct3");
String ref4 = new String("obejct4");
wMap.put(ref1, "cacheObject1");
wMap.put(ref2, "cacheObject2");
map.put(ref3, "cacheObject3");
map.put(ref4, "cacheObject4");
System.out.println("String引用ref1,ref2,ref3,ref4 消失");
}
public static void testWeakHashMap() {
System.out.println("WeakHashMap GC之前");
for (Object o : wMap.entrySet()) {
System.out.println(o);
}
try {
System.gc();
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("WeakHashMap GC之后");
for (Object o : wMap.entrySet()) {
System.out.println(o);
}
}
public static void testHashMap() {
System.out.println("HashMap GC之前");
for (Object o : map.entrySet()) {
System.out.println(o);
}
try {
System.gc();
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("HashMap GC之后");
for (Object o : map.entrySet()) {
System.out.println(o);
}
}
}
结果:
String引用ref1,ref2,ref3,ref4 消失
WeakHashMap GC之前
obejct2=cacheObject2
obejct1=cacheObject1
WeakHashMap GC之后
HashMap GC之前
obejct4=cacheObject4
obejct3=cacheObject3
Disconnected from the target VM, address: ‘127.0.0.1:51628’, transport: ‘socket’
HashMap GC之后
obejct4=cacheObject4
obejct3=cacheObject3
分析:
⑧监听器和回调
3、内存泄露案例分析
①案例代码
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) { //入栈
ensureCapacity();
elements[size++] = e;
}
//隐式内存泄露代码↓
public Object pop() { //出栈
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
②分析
③解决办法
将代码中的pop()方法变成如下方法:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];//赋值给一个变量
elements[size] = null;//制空之前不要的元素为null
return result;//返回接下来的位置
}
六、支持使用OQL语言查询对象信息
介绍:
在Eclipse MAT中如何用:
例子:
- select * from java.util.ArrayList(列出所有的ArrayList对象信息)
- select v.elementData from java.util.ArrayList v(注意:elementData代表ArrayList中的数组,结果最终以数组形式将结果呈现出来)
- select objects v.elementData from java.util.ArrayList v(注意:elementData代表ArrayList中的数组,objects代表对象类型,所以最终以对象形式将结果呈现出来,同时展示出来的还有浅堆、深堆)
- select as retained set * from com.atguigu.mat.Student(得到对象的保留级)
- select * from 0x6cd57c828(0x6cd57c828是Student类的内存地址值)
- select * from char[] s where s.@length > 10(char型数组长度大于10的数组)
- select * from java.lang.String s where s.value != null(字符串值不为空的字符串信息)
- select toString(f.path.value) from java.io.File f(列出文件的路径值)
- select v.elementData.@length from java.util.ArrayList v(列出Arraylist对象中ArrayList中的数组长度)
1、SELECT子句
2、FROM子句
3、WHERE子句
4、内置对象与方法
七、JProfiler
1、基本概述
①介绍
JProfiler更适配IDEA
②特点
③主要功能
2、安装与配置
①下载与安装
②JProfiler中配置IDEA
1、IDE Integrations
2、选择合适的IDE版本
3、开始集成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RZag59Z-1628952215579)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210814202610569.png)]
4、正式集成
5、集成成功
6、点击OK即可
③IDEA集成JProfiler
一、安装JProfiler插件
方式1:在线安装
方式2、离线安装
首先下载插件:
准备离线安装:
正式离线安装:
注意:无论采用方式1还是方式2都需要重启IDEA
二、将JProfiler配置到IDEA中
3、具体使用
重点关注:线程、内存、CPU的情况
采样模式:
常见操作:
1、Starter Center
如果程序已经保存了Quick Attach ,那么即使下次程序运行的时候没有启动JProfiler,而我们启动JProfiler,然后找到该Quick Attach中的对应位置,点击就可以运行了
2、垃圾回收
3、 标记
4、手动刷新
5、Live memory中的Recorded Objects可以查看对象信息,根据View中的Change Liveness Mode来更改查看的对象类型
如果通过Telemetries中的Memory看到垃圾回收之后内存占用还是越来越多,那就需要注意内存泄露问题了,这个时候我们可以查看Live memory中的Recorded Objects中的对象信息,可以先查看Live Objects中的,也就是存活的对象,然后在查看Garbage Collected Objects,如果某对象只在Live Objects中出现,但是没有在Garbage Collected Objects中出现,那么说明该对象就没有进行垃圾回收,即该对象有可能造成内存泄露
6、保存堆快照dump文件
①遥感监测 Telemetries
Telemetries就是遥感监测的意思,一个概述性的内容
②内存视图 Live Memory
分析:
注意:
All Objects
后面的Size大小是浅堆大小
Record Objects
在判断内存泄露的时候使用,可以通过观察Telemetries中的Memory,如果里面出现垃圾回收之后
的内存占用逐步提高,这就有可能出现内存泄露问题,所以可以使用Record Objects查看,但是该分析默认不开启,毕竟占用CPU性能太多
③堆遍历 heap walker
如果通过内存视图 Live Memory已经分析出哪个类的对象不能进行垃圾回收,并且有可能导致内存溢出,如果想进一步分析,我们可以在该对象上点击右键,选择Show Selection In Heap Walker,如下图:
之后进行溯源,操作如下:
查看结果,并根据结果去看对应的图表:
以下是图表的展示情况:
④cpu视图 cpu views
具体使用:
1、记录方法统计信息
2、方法统计
3、具体分析
⑤线程视图 threads
具体使用:
1、查看线程运行情况
2、新建线程dump文件
⑥监视器&锁 Monitors&locks
4、案例分析
①案例1
良心循环
public class JProfilerTest {
public static void main(String[] args) {
//每一次执行玩一个while循环,就会销毁list
while (true){
ArrayList list = new ArrayList();
for (int i = 0; i 500; i++) {
Data data = new Data();
list.add(data);
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Data{
private int size = 10;
private byte[] buffer = new byte[1024 * 1024];//1mb
private String info = "hello,atguigu";
}
②案例2
内存泄露案例
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";
//这个属性的static修饰,不能销毁;内存中只能有一个每一个Bean对象的这个属性都指向唯一一个list
static ArrayList list = new ArrayList();
}
解释:
我们通过JProfiler来看一下,如下:
你可以看到内存一个劲的往上涨,但是就是没有下降的趋势,说明这肯定有问题,过不了多久就会出现OOM,我们来到Live memory中,先标记看一下到底是哪些对象在进行内存增长,等一小下看看会不会触发垃圾回收,如果不触发的话,我们自己来触发垃圾回收,之后观察哪些对象没有被回收掉,如下:
我上面点击了Mark Current,发现有些对象在持续增长,然后点击了一下Run GC,结果如下所示:
可以看出byte[]没有被回收,说明它是有问题的,我们点击Show Selection In Heap Walker,如下:
然后看一下该对象被谁引用,如下:
结果如下:
可以看出byte[]来自于Bean类是的list中,并且这个list是ArrayList类型的静态集合,所以找到了:static ArrayList list = new ArrayList();
发现Bean类里的list是静态
的,这不妥,因为我们的目的是while结束之后Bean对象被回收,并且Bena对象中的所有字段都被回收,但是list是静态的,那就是类的,众所周知,类变量随类而生,随类而灭,因此每次我们往list中添加值,都是往同一个list中添加值,因为每一个Bean对象里的list属性,都是静态引用唯一的list对象,所以这会造成list不断增大,并且不能回收,所以最终会导致OOM
八、Arthas
服务器线上的监控工具
1、基本概述
①背景
②概述
③基于哪些工具开发而来
④官方使用文档
https://arthas.aliyun.com/doc/quick-start.html
2、安装与使用
①安装
②工程目录
③启动
④查看进程
jps
⑤查看日志
cat ~/logs/arthas/arthas.log
⑥查看帮助
java -jar arthas-boot.jar -h
⑦web console
⑧退出
3、相关诊断指令
①基础指令
②jvm相关
命令列表:https://arthas.aliyun.com/doc/commands.html#id1
- dashboard
链接:https://arthas.aliyun.com/doc/dashboard.html
作用:当前系统的实时数据面板
- thread
链接:https://arthas.aliyun.com/doc/thread.html
作用:查看当前线程信息,查看线程的堆栈
- jvm
- 其他
③class/classloader相关
- sc
-
sm
-
jad
-
mc、redefine
- classloader
④monitor/watch/trace相关
方法执行监控等情况
命令列表:https://arthas.aliyun.com/doc/commands.html#id1
- monitor
- watch
-
trace
-
stack
- tt
⑤其他
- profiler/火焰图
profiler:https://arthas.aliyun.com/doc/profiler.html
- options
options:https://arthas.aliyun.com/doc/options.html
九、Java Misssion Control
1、历史
2、启动
3、概述
4、功能:实时监控JVM运行时的状态
5、Java Flight Recorder
①事件类型
②启动方式
-
方式1:使用-XX:StartFlightRecording=参数
-
方式2:使用jcmd的JFR.*子命令
-
方式3:JMC的JFR插件
具体使用:
1、启动飞行记录仪
2、启动飞行记录
3、 正式启动
③Java Flight Recorder 取样分析
代码:↓
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @author shkstart shkstart@126.com
* @create 2020 21:12
*/
public class OOMTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
while(true){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(100 * 50)));
}
}
}
class Picture{
private byte[] pixels;
public Picture(int length) {
this.pixels = new byte[length];
}
public byte[] getPixels() {
return pixels;
}
public void setPixels(byte[] pixels) {
this.pixels = pixels;
}
}
结果:↓
1、一般信息
2、内存
3、代码
4、线程
5、I/O
6、系统
7、事件