JVM(下篇)
作者:互联网
1、概述篇
1.1、大厂面试题
1.2、背景说明
1.3、调优概述
1.4、性能优化的步骤
(括号内的描述会感觉很low,冒号后面的就感觉很高级。)
性能监控
理解
一种以非强行或者非入侵方式收集或查看应用运营性能数据的活动。
监控通常是指一种在生产、质量评估或者开发环境下实施的带有预防或主动性的活动。
当应用相关干系人提出性能问题却没有提供足够多的线索时,首先我们需要进行性能监控,随后是性能分析。
监控哪些方面:
- GC 频繁
- cpu load 过高
- OOM
- 内存泄露
- 死锁
- 程序响应时间较长
性能分析
理解
从哪些方面分析
- 打印 GC 日志,通过 GCviewer 或者 http://gceasy.io 来分析异常信息
- 灵活运用命令行工具、jstack、jmap、jinfo 等
- dump 出堆文件,使用内存分析工具分析文件
- 使用阿里 Arthas、jconsole、JVisualVM 来实时查看 JVM 状态
- jstack 查看堆栈信息
性能调优
理解
一种为改善应用响应性或吞吐量而更改参数、源代码、属性配置的活动,性能调优是在性能监控、性能分析之后的活动。
如何调优
- 适当增加内存,根据业务背景选择垃圾回收器
- 优化代码,控制内存使用
- 增加机器,分散节点压力
- 合理设置线程池线程数量
- 使用中间件提高程序效率,比如缓存、消息队列等
- 其他……
1.5、性能评价/测试指标
概述
停顿时间(或响应时间)
提交请求和返回该请求的响应之间使用的时间,一般比较关注平均响应时间。常用操作的响应时间列表:
操作 | 响应时间 |
---|---|
打开一个站点 | 几秒 |
数据库查询一条记录(有索引) | 十几毫秒 |
机械磁盘一次寻址定位 | 4 毫秒 |
从机械磁盘顺序读取 1M 数据 | 2 毫秒 |
从 SSD 磁盘顺序读取 1M 数据 | 0.3 毫秒 |
从远程分布式换成 Redis 读取一个数据 | 0.5 毫秒 |
从内存读取 1M 数据 | 十几微妙 |
Java 程序本地方法调用 | 几微妙 |
网络传输 2Kb 数据 | 1 微妙 |
在垃圾回收环节中:
- 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
- -XX:MaxGCPauseMillis
吞吐量
- 对单位时间内完成的工作量(请求)的量度
- 在 GC 中:运行用户代码的事件占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)
- 吞吐量为 1-1/(1+n),其中-XX::GCTimeRatio=n
并发数
- 同一时刻,对服务器有实际交互的请求数
内存占用
- Java 堆区所占的内存大小
相互间的关系
以高速公路通行状况为例
- 吞吐量:每天通过高速公路收费站的车辆的数据
- 并发数:高速公路上正在行驶的车辆的数目
- 响应时间:车速
2、JVM监控及诊断工具 - 命令行篇
2.1、概述
性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。
Java 作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成 Java 应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络 I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。
体会 1:使用数据说明问题,使用知识分析问题,使用工具处理问题。
体会 2:无监控、不调优!
2.1.1. 简单命令行工具
在我们刚接触 java 学习的时候,大家肯定最先了解的两个命令就是 javac,java,那么除此之外,还有没有其他的命令可以供我们使用呢?
我们进入到安装 jdk 的 bin 目录,发现还有一系列辅助工具。这些辅助工具用来获取目标 JVM 不同方面、不同层次的信息,帮助开发人员很好地解决 Java 应用程序的一些疑难杂症。
Window系统下:
工具源码
访问网站
2.2、jps:查看正在运行的Java进程
2.2.1. 基本情况
jps(Java Process Status)
显示指定系统内所有的 HotSpot 虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。
说明:对于本地虚拟机进程来说,进程的本地虚拟机 ID 与操作系统的进程 ID 是一致的,是唯一的。
2.2.2. 测试
运行程序,别让jvm结束就行
每次jps都是起了一个新的进程
2.2.3. 基本语法
基本使用语法为:jps [options] [hostid]
我们还可以通过追加参数,来打印额外的信息。
options 参数
- -q:仅仅显示 LVMID(local virtual machine id),即本地虚拟机唯一 id。不显示主类的名称等
- -l:输出应用程序主类的全类名 或 如果进程执行的是 jar 包,则输出 jar 完整路径
- -m:输出虚拟机进程启动时传递给主类 main()的参数
- -v:列出虚拟机进程启动时的 JVM 参数。比如:-Xms20m -Xmx50m 是启动程序指定的 jvm 参数。
说明:以上参数可以综合使用。
补充:如果某 Java 进程关闭了默认开启的 UsePerfData 参数(即使用参数-XX:-UsePerfData),那么 jps 命令(以及下面介绍的 jstat)将无法探知该 Java 进程。
hostid 参数
RMI 注册表中注册的主机名。如果想要远程监控主机上的 java 程序,需要安装 jstatd。
对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到 IP 地址欺诈攻击。
如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行 jstatd 服务器,而是在本地使用 jstat 和 jps 工具。
具体参数演示:
-q只显示进程
-l包含全类名
-m
换行
程序传递参数,然后运行
-m参数
看到了传递的参数,相关jar包也可能看到
jvm参数,然后启动
-v
可以综合使用(或者jps -lm)
mlvV参数可以写在一起,例如(jps -mlvV);参数q是比较独立的,如果要想和其他参数一起发挥作用,应该分开写(例如 jps -l -q)
jvm参数,关闭,运行
jps,就没有那个程序了
2.3、jstat:查看JVM统计信息
(这个很重要)
基本情况
jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具
。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。
在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题
。
官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
jstat和jstat -help是一样的
基本语法
概述
其中vmid是进程id号,也就是jps之后看到的前面的号码,如下:
预览图
相关参数
option 参数
选项 option 可以由以下值构成。
类装载相关的:
- -class:显示 ClassLoader 的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
垃圾回收相关的:
- -gc:显示与 GC 相关的堆信息。包括 Eden 区、两个 Survivor 区、老年代、永久代等的容量、已用空间、GC 时间合计等信息。
- -gccapacity:显示内容与-gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间。
- -gcutil:显示内容与-gc 基本相同,但输出主要关注已使用空间占总空间的百分比。
- -gccause:与-gcutil 功能一样,但是会额外输出导致最后一次或当前正在发生的 GC 产生的原因。
- -gcnew:显示新生代 GC 状况
- -gcnewcapacity:显示内容与-gcnew 基本相同,输出主要关注使用到的最大、最小空间
- -geold:显示老年代 GC 状况
- -gcoldcapacity:显示内容与-gcold 基本相同,输出主要关注使用到的最大、最小空间
- -gcpermcapacity:显示永久代使用到的最大、最小空间。
JIT 相关的:
- -compiler:显示 JIT 编译器编译过的方法、耗时等信息
- -printcompilation:输出已经被 JIT 编译的方法
小试牛刀
启动
类装载相关的
class
这就是类装载的功能,通过-class来体现的
JIT相关的
compiler
翻译结果:编译的有91,失败的多少,耗时,失败的类型、方法等
printcompilation
翻译结果:append方法被编译过了
垃圾回收相关的
gc
参数具体的含义
GCT是总的gc时间,是YGCT+FGCT的时间。
option参数除外的其他参数
interval
,每隔一秒打印一次
count
,打印10次就结束
t
,进程启动后,经历的时间,单位是秒
h
,每隔多少行数据就打印一次表头(-h3,是每隔3条数据就打印一次表头)
例子
jvm参数:-Xms60m -Xmx60m -XX:SurvivorRatio=8
60M空间,newRatio=2,即新生代=20M(SurvivorRatio=8,所以S0、S1、Eden分别是2M、2M、16M),老年代=40M。上图YGC一次。
gcutil是显示占比的情况,FGC发生了,FGC后还是内存不够就会OOM
OOM了监控也就终止了
发生GC的原因
生产环境是不能用GUI界面的工具的,只能用jstat这些命令行工具监控。
截取两行,然后获取到启动的时间时间段假设为10,GCT总时间在这期间花费了假设0.04,然后0.04 / 10这个比例超过20%就是压力较大。
如下图:
2.4、jinfo:实时查看和修改JVM配置参数
概览图:
基本情况
jps可以看到设置过的参数,没有设置过就要用jinfo
基本语法
查看
jinfo -sysprops 进程id
:可以查看由System.getProperties()取得的参数
jinfo -flags 进程id
:查看曾经赋过值的一些参数
举例:
jinfo -flag 参数名称 进程id
:查看某个java进程的具体参数信息
修改
Linux上运行 java -XX:+PrintFlagsFinal -version | grep manageable
Window系统运行 java -XX:+PrintFlagsFinal -version | findstr manageable
针对boolean类型
jinfo -flag [+|-]参数名称 进程id
PID可以通过jps命令查看,如果使用+号,那就可以让该参数起作用,否则使用-号就让该参数不起作用,具体例子如下:
针对非boolean类型
jinfo -flag 参数名称=参数值 进程id
PID可以通过jps命令查看,如果使用+号,那就可以让该参数起作用,否则使用-号就让该参数不起作用,具体例子如下:
注:程序退出之后,这个修改值就会失效,即不支持持久化
拓展
java -XX:PrintFlagsFinal
值前面添加冒号:的是修改之后的值,没有添加的都是没有发生改变的初始值
2.5、jmap:导出内存映像文件&内存使用情况
概览图
基本情况
基本语法
-dump
-heap
:输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等
-histo
:输出堆中对象的同级信息,包括类、实例数量和合计容量;特别的:-histo:live只统计堆中的存活对象
使用1:导出内存映像文件
概述
说明:
1、(自动方式)通常在写Heap Dump文件前会触发一次Full GC,所以heap dump文件里保存的都是Full GC后留下的对象信息。而手动不会在Full GC之后生成Dump
2、由于生成dump文件比较耗时,因此大家需要耐心等待,尤其是大内存镜像生成dump文件则需要耗费更长的时间来完成。
3、使用手动方式生成dump文件,一般指令执行之后就会生成,不用等到快出现OOM的时候
4、使用自动方式生成dump文件,当出现OOM之前先生成dump文件
5、如果使用手动方式,生成堆中存活对象的dump文件是比较小的,便于传输和分析
手动方式
说明:
<filename.hprof>中的filename是文件名称,而.hprof是后缀名,<***>代表该值可以省略<>,当然后面的
format=b表示生成的是标准的dump文件,用来进行格式限定
具体例子如下:
生成堆中所有对象的快照:
jmap -dump:format=b,file=d:\1.hprof 11696
生成堆中存活对象的快照:
jmap -dump:format=live,b,file=d:\1.hprof 11696
其中file=后面的是生成的dump文件地址,最后的11696是进程id,可以通过jps查看
例子:
连续生成3个(防止程序结束了)。dump是堆转储,format=b是标准的格式
生成堆中存活对象的快照:
文件,是二进制的。1、2、3文件是越来越大,4(活着的对象)一般都是比1、2、3小的(生产环境dump出活着的对象就行,导致内存不足也是因为 活着的对象无法回收),但是我们这里变大是因为不断的产生新的对象。
自动方式
自动方式,如果发生OOM,会自动dump,生成dump文件
复制比如的参数
出现OOM前就dump出来了
使用2:显示堆内存相关信息
jmap -heap 进程id
1、输出的是时间点上的堆信息,而jstat后面可以添加参数,可以指定时间动态观察数据改变情况,而图形化界面工具,例如jvisualvm等,它们可以用图表的方式动态展示出相关信息,更加直观明了
jmap -histo 进程id
输出堆中对象的同级信息,包括类、实例数量和合计容量,也是这一时刻的内存中的对象信息
例子如下:
jmap -heap 3540 > a.txt
jmap -histo 3540 > b.txt
a.txt文件
b.txt文件
跟可视化 的工具看到类似
使用3:其他作用(了解)
这两个指令仅linux/solaris平台有效,所以无法在windows操作平台上演示,并且使用比较小众,不在多说
小结
2.6、jhat:JDK自带堆分析工具(了解)
概览图
jhat命令在jdk9及其之后就被移除了,官方建议使用jvisualvm代替jhat,所以该指令只需简单了解一下即可
基本情况
jhat就是分析二进制dump文件工具。 VisualVM比jhat功能强大,一个是可视化的,一个是命令行的。(查看已经dump出来的文件不需要在命令行执行,即不需要在Linux下执行,所以可视化更方便。jhat不会在生产环境执行的)
会启动一个微型的服务
分析文件CPU会升高
访问
堆空间的直方图
跟前面看到的JProfiler看到的类似
OQL查询
长度大于100,快速定位大文件,跟hibernate的HQL语法很像
基本语法
jhat就简单介绍,后面用VisualVM。
2.7、jstack:打印JVM中线程快照
jstack是打印线程的,jhat是打印堆空间的。
基本情况
基本语法
例子:
package com.atguigu.jstack;
import java.util.Map;
import java.util.Set;
/**
* 演示线程的死锁问题
*
* @author shkstart
* @create 下午 3:20
*/
public class ThreadDeadLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
运行,实际代码量会很大,很难一眼看出来死锁,长时间没有执行结果,就可以用工具看看是不是死锁了
其他线程都是正常的Runnable
守护线程,即垃圾回收线程,是正常的
线程1的锁被线程0持有了,反过来也是,就找到死锁
package com.atguigu.jstack;
/**
* 演示线程:TIMED_WAITING
*
* @author shkstart
* @create 15:28
*/
public class TreadSleepTest {
public static void main(String[] args) {
System.out.println("hello - 1");
try {
Thread.sleep(1000 * 60 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello - 2");
}
}
运行
等待,因为在睡眠
package com.atguigu.jstack;
/**
* 演示线程的同步
*
* @author shkstart
* @create 15:31
*/
public class ThreadSyncTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
if (number <= 100) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
} else {
break;
}
}
}
}
}
线程2在阻塞,线程1在睡眠,这就是线程的监控
加-l参数,多一些锁的附加信息
补充
package com.atguigu.jstack;
import java.util.Map;
import java.util.Set;
/**
* @author shkstart
* @create 15:51
*/
public class AllStackTrace {
public static void main(String[] args) {
Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();
Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
for(Map.Entry<Thread, StackTraceElement[]> en : entries){
Thread t = en.getKey();
StackTraceElement[] v = en.getValue();
System.out.println("【Thread name is :" + t.getName() + "】");
for(StackTraceElement s : v){
System.out.println("\t" + s.toString());
}
}
}
}
复制这代码放到ThreadDeadLock
package com.atguigu.jstack;
import java.util.Map;
import java.util.Set;
/**
* 演示线程的死锁问题
*
* @author shkstart
* @create 下午 3:20
*/
public class ThreadDeadLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();//追踪当前进程中的所有的线程
Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
for(Map.Entry<Thread, StackTraceElement[]> en : entries){
Thread t = en.getKey();
StackTraceElement[] v = en.getValue();
System.out.println("【Thread name is :" + t.getName() + "】");
for(StackTraceElement s : v){
System.out.println("\t" + s.toString());
}
}
}
}).start();
}
}
运行
用java代码也能大概看出来是死锁,但不是很直白。
2.8、jcmd:多功能命令行
一个顶多个,真的很强
概览图
基本情况
类似于jps
jcmd -l
:列出所有的JVM进程
jcmd 进程号 help
:针对指定的进程,列出支持的所有具体命令
jcmd 进程号 具体命令
根据命令来替换之前的那些操作:
Thread.print 可以替换 jstack指令
GC.class_histogram 可以替换 jmap中的-histo操作
GC.heap_dump 可以替换 jmap中的-dump操作
GC.run 可以查看GC的执行情况
VM.uptime 可以查看程序的总执行时间,可以替换jstat指令中的-t操作
VM.system_properties 可以替换 jinfo -sysprops 进程id
VM.flags 可以获取JVM的配置参数信息
相当于jstat的操作
相当于jmap -histo的操作
相当于jmap -dump
进行的执行时间(相当于jstat -t )
相当于jinfo -sysprops pid的操作
JVM参数
2.9、jstatd:远程主机信息收集
3、JVM监控及诊断工具 - GUI篇
3.1、工具概述
jvisualvm是自带的,Visual VM是独立下载的,都是同一个。
JMC本来是Jrocket VM,后来被Oracle收购了。
3.2、jConsole
概览图
基本概述
启动
方式一:在jdk安装目录中找到jconsole.exe,双击该可执行文件就可以
方式二:打开DOS窗口,直接输入jconsole就可以了
package com.atguigu.jconsole;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @create 2020 17:51
*/
public class HeapInstanceTest {
byte[] buffer = new byte[new Random().nextInt(1024 * 100)];
public static void main(String[] args) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
while (true) {
list.add(new HeapInstanceTest());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行
运行jconsole(进程显示就是通过jps实现的)
三种连接方式
Local
使用JConsole连接一个正在本地系统运行的JVM,并且执行程序的用户和运行JConsole的用户需要是同一个用户
。JConsole使用文件系统的授权通过RMI连接器链接到平台的MBean的服务器上。这种从本地连接的监控能力只有Sun的JDK具有。
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的应用
主要作用
1、概览
2、内存
这些区域可以点击,折线会跟着变化
可以强制GC
如果OOM了
看到满了
3、根据线程检测死锁
package com.atguigu.jconsole;
import java.util.Map;
import java.util.Set;
/**
* 演示线程的死锁问题
*
* @author shkstart
* @create 下午 3:20
*/
public class ThreadDeadLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
监测死锁
告诉我们死锁了
4、类
5、VM 概要
3.3、Visual VM
概览图
基本概述
多合一就是多个工具集合一起
使用:
在jdk安装目录中找到jvisualvm.exe,然后双击执行即可
打开DOS窗口,输入jvisualvm就可以打开该软件
启动整体界面
插件安装
第一种,Visual VM本身作为一种插件,在它的内部也可以安装其他的插件,例如安装Visual GC
之前就讲过这个Visual GC
第一种还可以在工具内安装,前面是GitHub下载安装
要装上visual gc
第二种是在IDEA上安装Visual VM这个插件
需要配置,不然点击图标无法正确打开(两个路径)
点击直接使用
连接方式
远程连接可以可视化查看生产环境的JVM,后面案例中讲(一开始以为生成环境只能用命令行呢)
主要功能
1.生成/读取堆内存快照
一、生成堆内存快照
1、方式1:
2、方式2:
注意:
生成堆内存快照如下图:
这些快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:
二、装入堆内存快照
2.查看JVM参数和系统属性
3.查看运行中的虚拟机进程
4.生成/读取线程快照
一、生成线程快照
1、方式1:
2、方式2:
注意:
生成线程快照如下图:
这些快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:
二、装入线程快照
5.程序资源的实时监控
6.其他功能
JMX代理连接
远程环境监控
CPU分析和内存分析
例子
package com.atguigu.jvisualvm;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @author shkstart shkstart@126.com
* @create 2020 21:12
*/
public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> 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];
}
}
package com.atguigu.jvisualvm;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @author shkstart shkstart@126.com
* @create 2020 17:51
*/
public class HeapInstanceTest {
byte[] buffer = new byte[new Random().nextInt(1024 * 100)];
public static void main(String[] args) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
while (true) {
list.add(new HeapInstanceTest());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
两个例子都运行,然后监控
概述
jinfo命令可以查看或者jps也可以
jinfo命令 -sysprops PID
可以执行垃圾回收
没有颜色的线程就是终止了(之前是运行的)。
还有不同颜色表示不同的线程状态
需要安装插件
HeapInstanceTest已经OOM了
因为新生代想放老年代,老年代满了
dump的方式
或者
dump出来只是一个临时快照,要保存就需要另存为
删除只是删除临时快照,另存为的还在
查看dump文件(dump文件一般都很大,随随便便就几百M)
双击具体的类会跳转到实例数
OQL
比较
当前同一个目录下的另一个
详细的数据信息
线程快照文件
两线程都处于监控状态,检测到死锁
也可以右键
查看
装入
抽样器
热点 - 方法 就是占用CPU时间比较长的方法(这个功能可以呀)
线程多还可以过滤
因为sleep,所以占用长
所以main方法占用CPU比较长
可以快照一下保存当前状态
CPU占有率高,就可以这样采样,看看是什么原因造成的。
内存采样
OOM或者Full GC次数多就可以查看哪个线程的数据占用内存多
也可以快照保存
3.4、eclipse MAT
MAT主要就是专门分析dump文件,更擅长dump文件分析。
概览图
基本概述
package com.atguigu.mat;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @author shkstart shkstart@126.com
* @create 2020 21:12
*/
public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> 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];
}
}
参数,运行
点finish
多个一个压缩包
也可以根据正常运行的线程生成dump文件
类似于jps的操作
打开上面刚生成的dump文件
点击finish
小图标
分别点击
通过图形列举最大的对象
例子中的图很单一,但实际项目中,图很丰富的,例如:
分析先从大的内存分析,因为泄露的可能性更大。
重复类(被多个加载器加载的类)
这个报告就是一开始运行生成的那个
Objects就是对象的个数;shallow heap就是浅堆,即一个对象消耗内存的大小,不包括其引用的对象的对象大小;深堆就是该对象被回收,相关的内存能回收多少。大概理解一下。
比如,id占大小,name、history引用占用大小,都是浅堆,但是name、history指向的对象的大小不算在浅堆中
点击一下,左边就有相关信息
分组(这个有用)
默认分组
排序
正则搜索(这个有用)
怀疑一个对象就可以跟踪GC Roots,排除虚引用、弱引用、软引用,即显示强引用
看看谁引用,引用是否合理
比较
看看哪些对象增加的快,排序
获取堆dump文件
dump文件内存
两点说明
获取dump文件
分析堆dump文件
histogram
:展示了各个类的实例数目以及这些实例的Shallow heap或者Retained heap的总和
l功能类似 jmap -histo pid
图标:
具体内容:
thread overview
作用:
1、查看系统中的Java线程
2、查看局部变量的信息
图标
具体信息:
local就是局部变量
里面数组的名字叫elementData,数组内就是一个个的Picture
展开,sleep
内存泄露疑点报告
a的生命周期很长,且大小不大(才38.5MB),但是a引用了b,且b很大(可能1000MB),b本来出了括号就应该回收(生命周期不应该那么长),由于a引用了,导致b无法及时被回收,这样也认为内存泄露,这就是广义的内存泄露。内存泄露有狭义和广义之分。
Object数组就是他
后面详细讲。
获得对象互相引用的关系
outgoing就是引用了谁,incoming就是谁引用了我。
with outgoing references
图示:
结果:
with incoming references
图示:
结果:
只有数组引用了(假如还有一个静态变量引用了Picture,这时候就可以分析这个引用是否应该存在,要么释放引用,要么改成弱引用等)
对照一下代码
浅堆与深堆
shallow heap
通过Mat工具分析
retained heap
注意:
当前深堆大小 = 当前对象的浅堆大小 + 对象中所包含对象的深堆大小
补充:对象实际大小
练习
B深堆大小和实际对象大小都是BCD
案例分析:StudentTrace
/**
* 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。
* 它由三个部分组成:Student、WebPage和StudentTrace三个类
*
* -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=c:\code\student.hprof
* @create 16:11
*/
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;
}
}
运行(参数:生成dump文件)
24 = int 4字节 + String引用 4字节 + history引用 4字节 + 对象头 8字节 = 20字节 + 8字节对齐
回收能回收多少就看深堆大小的内存
三个student的深度大小不同是由于3整除的多一些,7整除少一些
深堆的计算过程
2280字节的大小只能该学生访问的所有网页,有些能被7整除的数也能被其他数整除,该学生访问的所有网页的深堆就没有2280字节那么大了。(实际是1288,即elementData的深堆大小)
该学生Lily被回收了,他的WebPage是不是就会被回收,就看incoming,看看是否有其他人都访问该WebPage
发现有3个学生,所以Lily回收,该WebPage不会被回收
右键看下incoming
只有Lily,所以Lily回收了,该WebPage就会被回收
支配树
引用图:
访问E要经过C,C就是E的支配者
D不是H的支配者
根据对象引用图画出支配树(C是DEFG的支配者)
画出支配树之后,就可以知道要是回收C,C所支配的都要回收
注意:
跟随我一起来理解如何从“对象引用图---》支配树”,首先需要理解支配者(如果要到达对象B,毕竟经过对象A,那么对象A就是对象B的支配者,可以想到支配者大于等于1),然后需要理解直接支配者(在支配者中距离对象B最近的对象A就是对象B的直接支配者,你要明白直接支配者不一定就是对象B的上一级,然后直接支配者只有一个),然后还需要理解支配树是怎么画的,其实支配树中的对象与对象之间的关系就是直接支配关系,也就是上一级是下一级的直接支配者,只要按照这样的方式来作图,肯定能从“对象引用图---》支配树”
在Eclipse MAT工具中如何查看支配树:
正则搜索
主线程的三个学生
注意区别
Lily只有8个,这就是支配树
前面说错了,之前说Size是dump文件的大小,其实是堆空间的大小。
案例:Tomcat堆溢出分析
点击最大的空间,右键,看看都引用了谁
看下具体的session,怀疑Tomcat收到大量的session导致的
OQL查询
验证了有大量的session(9千多个),下一步就要验证session是不是都是短时间内被创建的
随便选一个session,属性信息有创建时间、结束时间,做差就是存活时间,下图是相差1毫秒
OQL查询,列出来所有的创建时间,两次排序 (拿到最早的创建时间和最后一次的创建时间,做差就能得到全部创建session的总时间段)
每个案例的思路不一样的,但是分析的技术都是一样的。
3.5、补充1:再谈内存泄露
内存泄露的理解与分析
两个都是 是,就不是内存泄露。 是 - 否,就是内存泄露; 否 - 否,第一个否,第二个问题就不用问了。
偶然发生,比如资源关闭问题。
隐式泄露,就是广义的内存泄露。
Java中内存泄露的8种情况
有些面试会要求写出内存泄露的例子,写出内存泄露不是为了让内存泄露,而是为了熟悉或者证明遇到过或解决过内存泄露的问题。
msg放到方法内部定义就行,作用域不用那么大,定义在外面需要回收UsingRandom对象才会回收msg。
如果必须要定义在外面,最后不用了要设置为null,这个方法我之前有试过。
package com.atguigu.memoryleak;
import java.util.HashSet;
/**
* 演示内存泄漏
* @author shkstart
* @create 14:47
*/
public class ChangeHashCode1 {
public static void main(String[] args) {
HashSet<Point> hs = new HashSet<Point>();
Point cc = new Point();
cc.setX(10);//hashCode = 41
hs.add(cc);
cc.setX(20);//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 +
'}';
}
}
打印出来两个相同的对象(本来是无法存入两个相同的对象的,但是中途修改了hash值,就存进去了,是否相同是根据hash值来的)
package com.atguigu.memoryleak;
import java.util.HashSet;
/**
* 演示内存泄漏
*
* @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 + '\'' +
'}';
}
}
hash是根据name值来的
运行
打印
注释掉就能删除了
运行
分析
Map判断对象是否一样跟重写的hashcode、equals方法有关系。
结论:
package com.atguigu.memoryleak;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
/**
* 演示内存泄漏
*
* @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
**/
WeakHashMap在执行GC后就清理掉了,HashMap在执行GC之后,没有被清理掉。
内存泄露案例分析
隐秘的泄露问题
package com.atguigu.memoryleak;
import java.util.Arrays;
import java.util.EmptyStackException;
/**
* @author shkstart
* @create 15:05
*/
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]; // 只是移动了游标,但是对象引用还在,就无法被回收
}
// 正确的写法
// public Object pop() {
// if (size == 0)
// throw new EmptyStackException();
// Object result = elements[--size];
// elements[size] = null;
// return result;
// }
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
移动端的例子
安卓端一个Activity就是一个页面,在TestActivity页面当点击返回的时候,TestActivity就可以被回收了,但是由于新的线程持有了TestActivity类的静态变量的key这个锁,而这个新的线程一直继续,所以就会导致TestActivity无法被回收。
对dump文件分析
3.6、补充2:支持使用OQL语言查询对象信息(了解)
按F1
语法
这就是关于MAT的使用
文档
英文的重要。
写好OQL,F5就能查询
结果集以对象的方式呈现
从Student中得到保留集
使用地址查找是唯一的,如果类名,可能有多个
长度大于10的数组
3.7、JProfiler
概览图
基本概述
介绍
特点
界面
主要功能
安装与配置
下载与安装
JProfiler中配置IDEA
1、IDE Integrations
2、选择合适的IDE版本
3、开始集成
4、正式集成
5、集成成功
6、点击OK即可
IDEA集成JProfiler
离线
配置
点击左边的就会运行
选中要运行的程序,然后运行JProfiler
OK就行
手动启动OOMTest
打开独立的JProfiler
点击分析已经保存,就可以选择之前保存的
也可以
选择jvm进程就会进入
具体使用
数据采集方式
内存泄露分析用Sampling足够了,正在运行的Java程序,推荐用Sampling模式。
整体视图
绿色是空闲的,蓝色是占用的
可以GC
内存区域
对象、数组内存活动的表,这里没有数据 点击一下
类的个数,绿色、蓝色看示意图
源代码
遥感监测
其中Telemetries就是遥感监测的意思
内存视图 Live Memory
这功能主要是分析这三个问题
这俩都比较大,就可能是第一个问题;个数不多,但Size大,就可能是第二个问题;
直方图(Size是浅堆)
可以标记当前时间点,然对比一下数量是多了还是少了
包的形式
更新可以手动点击更新
手动启动(因为这个启动会导致jvm性能降低)(分析内存泄露可以开启)
一般GC后,内存变化是折线图,如果把折线图的最低点连接起来像增长的线性图,就可能是内存泄露了,因为线性图说明GC没有回收到什么垃圾。这是从Memory观察到的,如果怀疑是内存泄露就可以开启Recorded Objects
活着的对象、垃圾收集的对象、两者都有的
这都是存活的
可以进行垃圾收集的对象
没有对象被回收
GC
再看一下
就有数据了,Picture是没有进行过回收的
这时候就可以定位到Picture没有被回收,就可以怀疑是Picture内存泄露
追踪
堆遍历 heap walker
如果通过内存视图 Live Memory已经分析出哪个类的对象不能进行垃圾回收,并且有可能导致内存溢出,如果想进一步分析,我们可以在该对象上点击右键,选择Show Selection In Heap Walker,如下图:
进行溯源,操作如下:
最后定位到是main方法引用了(这里比较简单)
还可以通过图表查看
GC无法回收Picture,这就可以看到是谁引用了,到底是不是内存泄露问题就看下代码就行。
关掉后会提示是否保存session,点击Processd
重新打开
选择就行
重新打开 OOMTest
可以生成堆转储快照(点击图标)
这就是离线版堆存储dump文件
生成heap当前的快照
这就是用JProfiler导出快照。
cpu视图 cpu views
CPU默认不会追踪,需要追踪就手动点开,因为追踪会影响性能
用官方例子
点击开启
具体使用:
1、记录方法统计信息
2、方法统计
3、具体分析
因为一个方法的执行时间越长,那就是说对CPU的占用就越多,JProfiler就通过方法的执行时间来刻画对CPU的使用情况。
各个线程
线程状态
注:对CPU的监控比内存的少。
线程视图 threads
具体使用:
1、查看线程运行情况
活的死的线程都有
2、新建线程dump文件
运行死锁的程序
监视器&锁 Monitors&locks
案例分析
案例1
package com.atguigu.jprofiler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
/**
* 功能演示测试
* @author shkstart
* @create 12:19
*/
public class JProfilerTest {
public static void main(String[] args) {
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";
}
点击执行
蓝色部分曲折,说明有进行GC
每循环一次,list就释放了
正常的程序就是这样子的内存有增加,又有GC回收,这就比较良性的,如果GC后一直有趋于线性增加就可能会内存泄露了。
正常的程序就是这样子的内存有增加,又有GC回收,这就比较良性的,如果GC后一直有趋于线性增加就可能会内存泄露了。
案例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";
// ArrayList list = new ArrayList();
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();
发现list是静态的,这不妥,因为我们的目的是while结束之后Bean对象被回收,并且Bena对象中的所有字段都被回收,但是list是静态的,那就是类的,众所周知,类变量随类而生,随类而灭,因此每次我们往list中添加值,都是往同一个list中添加值,这会造成list不断增大,并且不能回收,所以最终会导致OOM
3、JVM监控及诊断工具 - GUI篇
3.1、工具概述
jvisualvm是自带的,Visual VM是独立下载的,都是同一个。
JMC本来是Jrocket VM,后来被Oracle收购了。
3.2、jConsole
概览图
基本概述
启动
方式一:在jdk安装目录中找到jconsole.exe,双击该可执行文件就可以
方式二:打开DOS窗口,直接输入jconsole就可以了
package com.atguigu.jconsole;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @create 2020 17:51
*/
public class HeapInstanceTest {
byte[] buffer = new byte[new Random().nextInt(1024 * 100)];
public static void main(String[] args) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
while (true) {
list.add(new HeapInstanceTest());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行
运行jconsole(进程显示就是通过jps实现的)
三种连接方式
Local
使用JConsole连接一个正在本地系统运行的JVM,并且执行程序的用户和运行JConsole的用户需要是同一个用户
。JConsole使用文件系统的授权通过RMI连接器链接到平台的MBean的服务器上。这种从本地连接的监控能力只有Sun的JDK具有。
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的应用
主要作用
1、概览
2、内存
这些区域可以点击,折线会跟着变化
可以强制GC
如果OOM了
看到满了
3、根据线程检测死锁
package com.atguigu.jconsole;
import java.util.Map;
import java.util.Set;
/**
* 演示线程的死锁问题
*
* @author shkstart
* @create 下午 3:20
*/
public class ThreadDeadLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
监测死锁
告诉我们死锁了
4、类
5、VM 概要
3.3、Visual VM
概览图
基本概述
多合一就是多个工具集合一起
使用:
在jdk安装目录中找到jvisualvm.exe,然后双击执行即可
打开DOS窗口,输入jvisualvm就可以打开该软件
启动整体界面
插件安装
第一种,Visual VM本身作为一种插件,在它的内部也可以安装其他的插件,例如安装Visual GC
之前就讲过这个Visual GC
第一种还可以在工具内安装,前面是GitHub下载安装
要装上visual gc
第二种是在IDEA上安装Visual VM这个插件
需要配置,不然点击图标无法正确打开(两个路径)
点击直接使用
连接方式
远程连接可以可视化查看生产环境的JVM,后面案例中讲(一开始以为生成环境只能用命令行呢)
主要功能
1.生成/读取堆内存快照
一、生成堆内存快照
1、方式1:
2、方式2:
注意:
生成堆内存快照如下图:
这些快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:
二、装入堆内存快照
2.查看JVM参数和系统属性
3.查看运行中的虚拟机进程
4.生成/读取线程快照
一、生成线程快照
1、方式1:
2、方式2:
注意:
生成线程快照如下图:
这些快照存储在内存中,当线程停止的时候快照就会丢失,如果还想利用,可以将快照进行另存为操作,如下图:
二、装入线程快照
5.程序资源的实时监控
6.其他功能
JMX代理连接
远程环境监控
CPU分析和内存分析
例子
package com.atguigu.jvisualvm;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @author shkstart shkstart@126.com
* @create 2020 21:12
*/
public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> 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];
}
}
package com.atguigu.jvisualvm;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @author shkstart shkstart@126.com
* @create 2020 17:51
*/
public class HeapInstanceTest {
byte[] buffer = new byte[new Random().nextInt(1024 * 100)];
public static void main(String[] args) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
while (true) {
list.add(new HeapInstanceTest());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
两个例子都运行,然后监控
概述
jinfo命令可以查看或者jps也可以
jinfo命令 -sysprops PID
可以执行垃圾回收
没有颜色的线程就是终止了(之前是运行的)。
还有不同颜色表示不同的线程状态
需要安装插件
HeapInstanceTest已经OOM了
因为新生代想放老年代,老年代满了
dump的方式
或者
dump出来只是一个临时快照,要保存就需要另存为
删除只是删除临时快照,另存为的还在
查看dump文件(dump文件一般都很大,随随便便就几百M)
双击具体的类会跳转到实例数
OQL
比较
当前同一个目录下的另一个
详细的数据信息
线程快照文件
两线程都处于监控状态,检测到死锁
也可以右键
查看
装入
抽样器
热点 - 方法 就是占用CPU时间比较长的方法(这个功能可以呀)
线程多还可以过滤
因为sleep,所以占用长
所以main方法占用CPU比较长
可以快照一下保存当前状态
CPU占有率高,就可以这样采样,看看是什么原因造成的。
内存采样
OOM或者Full GC次数多就可以查看哪个线程的数据占用内存多
也可以快照保存
3.4、eclipse MAT
MAT主要就是专门分析dump文件,更擅长dump文件分析。
概览图
基本概述
package com.atguigu.mat;
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
* @author shkstart shkstart@126.com
* @create 2020 21:12
*/
public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> 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];
}
}
参数,运行
点finish
多个一个压缩包
也可以根据正常运行的线程生成dump文件
类似于jps的操作
打开上面刚生成的dump文件
点击finish
小图标
分别点击
通过图形列举最大的对象
例子中的图很单一,但实际项目中,图很丰富的,例如:
分析先从大的内存分析,因为泄露的可能性更大。
重复类(被多个加载器加载的类)
这个报告就是一开始运行生成的那个
Objects就是对象的个数;shallow heap就是浅堆,即一个对象消耗内存的大小,不包括其引用的对象的对象大小;深堆就是该对象被回收,相关的内存能回收多少。大概理解一下。
比如,id占大小,name、history引用占用大小,都是浅堆,但是name、history指向的对象的大小不算在浅堆中
点击一下,左边就有相关信息
分组(这个有用)
默认分组
排序
正则搜索(这个有用)
怀疑一个对象就可以跟踪GC Roots,排除虚引用、弱引用、软引用,即显示强引用
看看谁引用,引用是否合理
比较
看看哪些对象增加的快,排序
获取堆dump文件
dump文件内存
两点说明
获取dump文件
分析堆dump文件
histogram
:展示了各个类的实例数目以及这些实例的Shallow heap或者Retained heap的总和
l功能类似 jmap -histo pid
图标:
具体内容:
thread overview
作用:
1、查看系统中的Java线程
2、查看局部变量的信息
图标
具体信息:
local就是局部变量
里面数组的名字叫elementData,数组内就是一个个的Picture
展开,sleep
内存泄露疑点报告
a的生命周期很长,且大小不大(才38.5MB),但是a引用了b,且b很大(可能1000MB),b本来出了括号就应该回收(生命周期不应该那么长),由于a引用了,导致b无法及时被回收,这样也认为内存泄露,这就是广义的内存泄露。内存泄露有狭义和广义之分。
Object数组就是他
后面详细讲。
获得对象互相引用的关系
outgoing就是引用了谁,incoming就是谁引用了我。
with outgoing references
图示:
结果:
with incoming references
图示:
结果:
只有数组引用了(假如还有一个静态变量引用了Picture,这时候就可以分析这个引用是否应该存在,要么释放引用,要么改成弱引用等)
对照一下代码
浅堆与深堆
shallow heap
通过Mat工具分析
retained heap
注意:
当前深堆大小 = 当前对象的浅堆大小 + 对象中所包含对象的深堆大小
补充:对象实际大小
练习
B深堆大小和实际对象大小都是BCD
案例分析:StudentTrace
/**
* 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。
* 它由三个部分组成:Student、WebPage和StudentTrace三个类
*
* -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=c:\code\student.hprof
* @create 16:11
*/
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;
}
}
运行(参数:生成dump文件)
24 = int 4字节 + String引用 4字节 + history引用 4字节 + 对象头 8字节 = 20字节 + 8字节对齐
回收能回收多少就看深堆大小的内存
三个student的深度大小不同是由于3整除的多一些,7整除少一些
深堆的计算过程
2280字节的大小只能该学生访问的所有网页,有些能被7整除的数也能被其他数整除,该学生访问的所有网页的深堆就没有2280字节那么大了。(实际是1288,即elementData的深堆大小)
该学生Lily被回收了,他的WebPage是不是就会被回收,就看incoming,看看是否有其他人都访问该WebPage
发现有3个学生,所以Lily回收,该WebPage不会被回收
右键看下incoming
只有Lily,所以Lily回收了,该WebPage就会被回收
支配树
引用图:
访问E要经过C,C就是E的支配者
D不是H的支配者
根据对象引用图画出支配树(C是DEFG的支配者)
画出支配树之后,就可以知道要是回收C,C所支配的都要回收
注意:
跟随我一起来理解如何从“对象引用图---》支配树”,首先需要理解支配者(如果要到达对象B,毕竟经过对象A,那么对象A就是对象B的支配者,可以想到支配者大于等于1),然后需要理解直接支配者(在支配者中距离对象B最近的对象A就是对象B的直接支配者,你要明白直接支配者不一定就是对象B的上一级,然后直接支配者只有一个),然后还需要理解支配树是怎么画的,其实支配树中的对象与对象之间的关系就是直接支配关系,也就是上一级是下一级的直接支配者,只要按照这样的方式来作图,肯定能从“对象引用图---》支配树”
在Eclipse MAT工具中如何查看支配树:
正则搜索
主线程的三个学生
注意区别
Lily只有8个,这就是支配树
前面说错了,之前说Size是dump文件的大小,其实是堆空间的大小。
案例:Tomcat堆溢出分析
点击最大的空间,右键,看看都引用了谁
看下具体的session,怀疑Tomcat收到大量的session导致的
OQL查询
验证了有大量的session(9千多个),下一步就要验证session是不是都是短时间内被创建的
随便选一个session,属性信息有创建时间、结束时间,做差就是存活时间,下图是相差1毫秒
OQL查询,列出来所有的创建时间,两次排序 (拿到最早的创建时间和最后一次的创建时间,做差就能得到全部创建session的总时间段)
每个案例的思路不一样的,但是分析的技术都是一样的。
3.5、补充1:再谈内存泄露
内存泄露的理解与分析
两个都是 是,就不是内存泄露。 是 - 否,就是内存泄露; 否 - 否,第一个否,第二个问题就不用问了。
偶然发生,比如资源关闭问题。
隐式泄露,就是广义的内存泄露。
Java中内存泄露的8种情况
有些面试会要求写出内存泄露的例子,写出内存泄露不是为了让内存泄露,而是为了熟悉或者证明遇到过或解决过内存泄露的问题。
msg放到方法内部定义就行,作用域不用那么大,定义在外面需要回收UsingRandom对象才会回收msg。
如果必须要定义在外面,最后不用了要设置为null,这个方法我之前有试过。
package com.atguigu.memoryleak;
import java.util.HashSet;
/**
* 演示内存泄漏
* @author shkstart
* @create 14:47
*/
public class ChangeHashCode1 {
public static void main(String[] args) {
HashSet<Point> hs = new HashSet<Point>();
Point cc = new Point();
cc.setX(10);//hashCode = 41
hs.add(cc);
cc.setX(20);//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 +
'}';
}
}
打印出来两个相同的对象(本来是无法存入两个相同的对象的,但是中途修改了hash值,就存进去了,是否相同是根据hash值来的)
package com.atguigu.memoryleak;
import java.util.HashSet;
/**
* 演示内存泄漏
*
* @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 + '\'' +
'}';
}
}
hash是根据name值来的
运行
打印
注释掉就能删除了
运行
分析
Map判断对象是否一样跟重写的hashcode、equals方法有关系。
结论:
package com.atguigu.memoryleak;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
/**
* 演示内存泄漏
*
* @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
**/
WeakHashMap在执行GC后就清理掉了,HashMap在执行GC之后,没有被清理掉。
内存泄露案例分析
隐秘的泄露问题
package com.atguigu.memoryleak;
import java.util.Arrays;
import java.util.EmptyStackException;
/**
* @author shkstart
* @create 15:05
*/
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]; // 只是移动了游标,但是对象引用还在,就无法被回收
}
// 正确的写法
// public Object pop() {
// if (size == 0)
// throw new EmptyStackException();
// Object result = elements[--size];
// elements[size] = null;
// return result;
// }
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
移动端的例子
安卓端一个Activity就是一个页面,在TestActivity页面当点击返回的时候,TestActivity就可以被回收了,但是由于新的线程持有了TestActivity类的静态变量的key这个锁,而这个新的线程一直继续,所以就会导致TestActivity无法被回收。
对dump文件分析
3.6、补充2:支持使用OQL语言查询对象信息(了解)
按F1
语法
这就是关于MAT的使用
文档
英文的重要。
写好OQL,F5就能查询
结果集以对象的方式呈现
从Student中得到保留集
使用地址查找是唯一的,如果类名,可能有多个
长度大于10的数组
3.7、JProfiler
概览图
基本概述
介绍
特点
界面
主要功能
安装与配置
下载与安装
JProfiler中配置IDEA
1、IDE Integrations
2、选择合适的IDE版本
3、开始集成
4、正式集成
5、集成成功
6、点击OK即可
IDEA集成JProfiler
离线
配置
点击左边的就会运行
选中要运行的程序,然后运行JProfiler
OK就行
手动启动OOMTest
打开独立的JProfiler
点击分析已经保存,就可以选择之前保存的
也可以
选择jvm进程就会进入
具体使用
数据采集方式
内存泄露分析用Sampling足够了,正在运行的Java程序,推荐用Sampling模式。
整体视图
绿色是空闲的,蓝色是占用的
可以GC
内存区域
对象、数组内存活动的表,这里没有数据 点击一下
类的个数,绿色、蓝色看示意图
源代码
遥感监测
其中Telemetries就是遥感监测的意思
内存视图 Live Memory
这功能主要是分析这三个问题
这俩都比较大,就可能是第一个问题;个数不多,但Size大,就可能是第二个问题;
直方图(Size是浅堆)
可以标记当前时间点,然对比一下数量是多了还是少了
包的形式
更新可以手动点击更新
手动启动(因为这个启动会导致jvm性能降低)(分析内存泄露可以开启)
一般GC后,内存变化是折线图,如果把折线图的最低点连接起来像增长的线性图,就可能是内存泄露了,因为线性图说明GC没有回收到什么垃圾。这是从Memory观察到的,如果怀疑是内存泄露就可以开启Recorded Objects
活着的对象、垃圾收集的对象、两者都有的
这都是存活的
可以进行垃圾收集的对象
没有对象被回收
GC
再看一下
就有数据了,Picture是没有进行过回收的
这时候就可以定位到Picture没有被回收,就可以怀疑是Picture内存泄露
追踪
堆遍历 heap walker
如果通过内存视图 Live Memory已经分析出哪个类的对象不能进行垃圾回收,并且有可能导致内存溢出,如果想进一步分析,我们可以在该对象上点击右键,选择Show Selection In Heap Walker,如下图:
进行溯源,操作如下:
最后定位到是main方法引用了(这里比较简单)
还可以通过图表查看
GC无法回收Picture,这就可以看到是谁引用了,到底是不是内存泄露问题就看下代码就行。
关掉后会提示是否保存session,点击Processd
重新打开
选择就行
重新打开 OOMTest
可以生成堆转储快照(点击图标)
这就是离线版堆存储dump文件
生成heap当前的快照
这就是用JProfiler导出快照。
cpu视图 cpu views
CPU默认不会追踪,需要追踪就手动点开,因为追踪会影响性能
用官方例子
点击开启
具体使用:
1、记录方法统计信息
2、方法统计
3、具体分析
因为一个方法的执行时间越长,那就是说对CPU的占用就越多,JProfiler就通过方法的执行时间来刻画对CPU的使用情况。
各个线程
线程状态
注:对CPU的监控比内存的少。
线程视图 threads
具体使用:
1、查看线程运行情况
活的死的线程都有
2、新建线程dump文件
运行死锁的程序
监视器&锁 Monitors&locks
案例分析
案例1
package com.atguigu.jprofiler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
/**
* 功能演示测试
* @author shkstart
* @create 12:19
*/
public class JProfilerTest {
public static void main(String[] args) {
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";
}
点击执行
蓝色部分曲折,说明有进行GC
每循环一次,list就释放了
正常的程序就是这样子的内存有增加,又有GC回收,这就比较良性的,如果GC后一直有趋于线性增加就可能会内存泄露了。
正常的程序就是这样子的内存有增加,又有GC回收,这就比较良性的,如果GC后一直有趋于线性增加就可能会内存泄露了。
案例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";
// ArrayList list = new ArrayList();
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();
发现list是静态的,这不妥,因为我们的目的是while结束之后Bean对象被回收,并且Bena对象中的所有字段都被回收,但是list是静态的,那就是类的,众所周知,类变量随类而生,随类而灭,因此每次我们往list中添加值,都是往同一个list中添加值,这会造成list不断增大,并且不能回收,所以最终会导致OOM
3.8、Arthas
基本概述
背景
概述
基于哪些工具开发而来
官方使用文档
https://arthas.aliyun.com/doc/quick-start.html
安装与使用
安装
工程目录
启动
查看日志
cat ~/logs/arthas/arthas.log
查看帮助
java -jar arthas-boot.jar -h
web console
退出
相关诊断指令
基础指令
jvm相关
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相关
monitor
watch
stack
trace
tt
其他
profiler/火焰图
profiler:https://arthas.aliyun.com/doc/profiler.html
options
options:https://arthas.aliyun.com/doc/options.html
3.9、Java Mission Control
概览图
历史
概述
启动
功能:实时监控JVM运行时的状态
添加要监控的
CPU等过高,可以触发报警
Java Flight Recorder
概述
事件类型
启动方式
方式1(了解):使用-XX:StartFlightRecording=参数
方式2(了解):使用jcmd的JFR.*子命令
方式3:通过JMC的JFR插件
添加参数:
-XX:+UnlockCommercialFeatures
-XX:+FlightRecorder
Java Flight Recorder 取样分析
哪些方法调用的多
3.10、其他工具
Flame Graphs(火焰图)
Tprofiler
Btrace
YourKit
JProbe
Spring Insight
4、JVM运行时参数
01-JVM参数选项
后两个是非标准的
类型一:标准参数选项
特点
以-开头
,比较稳定,后续版本基本不会变化
各种选项
直接在DOS窗口中运行java或者java -help可以看到所有的标准选项
C:\Users\Administrator>java
用法: java [-options] class [args...]
(执行类)
或 java [-options] -jar jarfile [args...]
(执行 jar 文件)
其中选项包括:
-d32 使用 32 位数据模型 (如果可用)
-d64 使用 64 位数据模型 (如果可用)
-server 选择 "server" VM
默认 VM 是 server.
-cp <目录和 zip/jar 文件的类搜索路径>
-classpath <目录和 zip/jar 文件的类搜索路径>
用 ; 分隔的目录, JAR 档案
和 ZIP 档案列表, 用于搜索类文件。
-D<名称>=<值>
设置系统属性
-verbose:[class|gc|jni]
启用详细输出
-version 输出产品版本并退出
-version:<值>
警告: 此功能已过时, 将在
未来发行版中删除。
需要指定的版本才能运行
-showversion 输出产品版本并继续
-jre-restrict-search | -no-jre-restrict-search
警告: 此功能已过时, 将在
未来发行版中删除。
在版本搜索中包括/排除用户专用 JRE
-? -help 输出此帮助消息
-X 输出非标准选项的帮助
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
按指定的粒度启用断言
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
禁用具有指定粒度的断言
-esa | -enablesystemassertions
启用系统断言
-dsa | -disablesystemassertions
禁用系统断言
-agentlib:<libname>[=<选项>]
加载本机代理库 <libname>, 例如 -agentlib:hprof
另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<选项>]
按完整路径名加载本机代理库
-javaagent:<jarpath>[=<选项>]
加载 Java 编程语言代理, 请参阅 java.lang.instrument
-splash:<imagepath>
使用指定的图像显示启动屏幕
补充内容:-server与-client
对于以上第2点,我们可以打开DOS窗口,输入java -version就可以看到64位机器上用的server模式,如下所示:
类型二:-X参数选项
特点
各种选项
直接在DOS窗口中运行java -X命令可以看到所有的X选项
C:\Users\Administrator>java -X
-Xmixed 混合模式执行 (默认)
-Xint 仅解释模式执行
-Xbootclasspath:<用 ; 分隔的目录和 zip/jar 文件>
设置搜索路径以引导类和资源
-Xbootclasspath/a:<用 ; 分隔的目录和 zip/jar 文件>
附加在引导类路径末尾
-Xbootclasspath/p:<用 ; 分隔的目录和 zip/jar 文件>
置于引导类路径之前
-Xdiag 显示附加诊断消息
-Xnoclassgc 禁用类垃圾收集
-Xincgc 启用增量垃圾收集
-Xloggc:<file> 将 GC 状态记录在文件中 (带时间戳)
-Xbatch 禁用后台编译
-Xms<size> 设置初始 Java 堆大小
-Xmx<size> 设置最大 Java 堆大小
-Xss<size> 设置 Java 线程堆栈大小
-Xprof 输出 cpu 配置文件数据
-Xfuture 启用最严格的检查, 预期将来的默认值
-Xrs 减少 Java/VM 对操作系统信号的使用 (请参阅文档)
-Xcheck:jni 对 JNI 函数执行其他检查
-Xshare:off 不尝试使用共享类数据
-Xshare:auto 在可能的情况下使用共享类数据 (默认)
-Xshare:on 要求使用共享类数据, 否则将失败。
-XshowSettings 显示所有设置并继续
-XshowSettings:all
显示所有设置并继续
-XshowSettings:vm 显示所有与 vm 相关的设置并继续
-XshowSettings:properties
显示所有属性设置并继续
-XshowSettings:locale
显示所有与区域设置相关的设置并继续
-X 选项是非标准选项, 如有更改, 恕不另行通知。
JVM的JIT编译模式相关的选项
JDK默认使用-Xmixed模式,证明如下:
特别地,-Xmx -Xms -Xss属于XX参数?
类型三:-XX参数选项
特点
作用:用于开发和调试JVM
分类
Boolean类型格式
非Boolean类型格式(key-value类型)
特别地
02-添加JVM参数选项
Eclipse
1、在空白处单击右键,选择Run As,在选择Run Configurations……
2、设置虚拟机参数
IDEA
1、Edit Configurations…
2、设置虚拟机参数
运行jar包
这是在java -jar demo.jar中的java -jar之间添加了虚拟机配置信息
java -Xms50m -Xmx50m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -jar demo.jar
通过Tomcat运行war包
Linux系统下可以在tomcat/bin/catalina.sh中添加类似如下配置:
JAVA_OPTS="-Xms512M -Xmx1024M"
Windows系统下载catalina.bat中添加类似如下配置:
set "JAVA_OPTS=-Xms512M -Xmx1024M"
程序运行过程中
使用jinfo -flag <name>=<value> <pid>
设置非Boolean类型参数
使用jinfo -flag [+|-]<name> <pid>
设置Boolean类型参数
03-常用的JVM参数选项
打印设置的XX选项及值
堆、栈、方法区等内存大小设置
栈
-Xss128k,等价于-XX:ThreadStackSize,设置每个线程的栈大小为128k
堆内存
-XX:SurvivorRatio=8
:设置年轻代中Eden区与一个Survivor区的比值,默认为8
只有显示使用Eden区和Survivor区的比例,才会让比例生效,否则比例都会自动设置,至于其中的原因,请看下面的-XX:+UseAdaptiveSizePolicy中的解释,最后推荐使用默认打开的-XX:+UseAdaptiveSizePolicy设置,并且不显示设置-XX:SurvivorRatio
-XX:+UseAdaptiveSizePolicy
:自动选择各区大小比例,默认开启
-XX:NewRatio=2
:设置老年代与年轻代(包括1个Eden区和2个Survivor区)的比值,默认为2
根据实际情况进行设置,主要根据对象生命周期来进行分配,如果对象生命周期很长,那么让老年代大一点,否则让新生代大一点
方法区
直接内存
-XX:MaxDirectMemorySize
:指定DirectMemory容量,若未指定,则默认与Java堆最大值一样
OutOfMemory相关的选项
垃圾收集器相关选项
查看默认的垃圾回收器
以上两种方式都可以查看默认使用的垃圾回收器,第一种方式更加准备,但是需要程序的支持;第二种方式需要去尝试,如果使用了,返回的值中有+号,否则就是-号
运行结果
Serial回收器
Parnew回收器
根据下图可知,该回收器最终将会没有搭档,那就相当于被遗弃了
Parallel回收器
注意:
Parallel回收器主打吞吐量,而CMS和G1主打低延迟
,如果主打吞吐量,那么就不应该限制最大停顿时间,所以-XX:MaxGCPauseMills不应该设置
-XX:MaxGCPauseMills中的调整堆大小通过默认开启的-XX:+UseAdaptiveSizePolicy来实现
-XX:GCTimeRatio用来衡量吞吐量,并且和-XX:MaxGCPauseMills矛盾,因此不会同时使用
CMS回收器
G1回收器
如果使用G1垃圾收集器,不建议设置-Xmn和-XX:NewRatio,毕竟可能影响G1的自动调节
怎么选择垃圾收集器
GC日志相关选项
常用参数
package com.atguigu.java;
import java.util.ArrayList;
/**
* -Xms60m -Xmx60m -XX:SurvivorRatio=8
* @author shkstart
* @create 14:27
*/
public class GCLogTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
byte[] arr = new byte[1024 * 100];//100KB
list.add(arr);
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
-verbose:gc
:输出日志信息,默认输出的标准输出;可以独立使用
-XX:+PrintGC
:等同于-verbose:gc,表示打开简化的日志;可以独立使用
-XX:+PrintGCDetails
:在发生垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域的分配情况;可以独立使用
-XX:+PrintGCTimeStamps
:程序启动到GC发生的时间秒数;不可以独立使用,需要配合-XX:+PrintGCDetails使用
-XX:+PrintGCDateStamps
:输出GC发生时的时间戳(以日期的形式,例如:2013-05-04T21:53:59.234+0800);不可以独立使用,可以配合-XX:+PrintGCDetails使用
-XX:+PrintHeapAtGC
:每一次GC前和GC后,都打印堆信息;可以独立使用
-XIoggc:<file>
:把GC日志写入到一个文件中去,而不是打印到标准输出中
GC参数对应的GC日志输出
###############-verbose:gc 或 -XX:+PrintGC ####################
[GC (Allocation Failure) 16303K->14194K(59392K), 0.0040576 secs]
[GC (Allocation Failure) 30519K->30520K(59392K), 0.0037994 secs]
[Full GC (Ergonomics) 30520K->30298K(59392K), 0.0160108 secs]
[Full GC (Ergonomics) 46642K->46301K(59392K), 0.0061445 secs]
########################## -XX:+PrintGCDetails ###########################
[GC (Allocation Failure) [PSYoungGen: 16303K->2016K(18432K)] 16303K->14198K(59392K), 0.0040885 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 18341K->2024K(18432K)] 30523K->30524K(59392K), 0.0042607 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 2024K->0K(18432K)] [ParOldGen: 28500K->30298K(40960K)] 30524K->30298K(59392K), [Metaspace: 3993K->3993K(1056768K)], 0.0354285 secs] [Times: user=0.08 sys=0.00, real=0.04 secs]
[Full GC (Ergonomics) [PSYoungGen: 16344K->5500K(18432K)] [ParOldGen: 30298K->40800K(40960K)] 46642K->46301K(59392K), [Metaspace: 3993K->3993K(1056768K)], 0.0064641 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 18432K, used 10379K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 63% used [0x00000000fec00000,0x00000000ff622d10,0x00000000ffc00000)
from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 40800K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 99% used [0x00000000fc400000,0x00000000febd82d8,0x00000000fec00000)
Metaspace used 4000K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 447K, capacity 460K, committed 512K, reserved 1048576K
##########################-XX:+PrintGCTimeStamps -XX:+PrintGCDetails#########################################
4.364: [GC (Allocation Failure) [PSYoungGen: 16303K->2040K(18432K)] 16303K->14270K(59392K), 0.0033008 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
9.614: [GC (Allocation Failure) [PSYoungGen: 18365K->1948K(18432K)] 30595K->30588K(59392K), 0.0043835 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
9.619: [Full GC (Ergonomics) [PSYoungGen: 1948K->0K(18432K)] [ParOldGen: 28640K->30298K(40960K)] 30588K->30298K(59392K), [Metaspace: 3996K->3996K(1056768K)], 0.0111210 secs] [Times: user=0.16 sys=0.00, real=0.02 secs]
14.934: [Full GC (Ergonomics) [PSYoungGen: 16344K->5500K(18432K)] [ParOldGen: 30298K->40800K(40960K)] 46642K->46301K(59392K), [Metaspace: 3996K->3996K(1056768K)], 0.0117331 secs] [Times: user=0.16 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 18432K, used 10379K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 63% used [0x00000000fec00000,0x00000000ff622d10,0x00000000ffc00000)
from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 40800K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 99% used [0x00000000fc400000,0x00000000febd8350,0x00000000fec00000)
Metaspace used 4003K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 447K, capacity 460K, committed 512K, reserved 1048576K
###########################-XX:+PrintGCDateStamps -XX:+PrintGCDetails#################################
2021-01-30T15:01:12.596+0800: [GC (Allocation Failure) [PSYoungGen: 16303K->2020K(18432K)] 16303K->14154K(59392K), 0.0051686 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2021-01-30T15:01:17.856+0800: [GC (Allocation Failure) [PSYoungGen: 18345K->2036K(18432K)] 30479K->30480K(59392K), 0.0037044 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-01-30T15:01:17.856+0800: [Full GC (Ergonomics) [PSYoungGen: 2036K->0K(18432K)] [ParOldGen: 28444K->30303K(40960K)] 30480K->30303K(59392K), [Metaspace: 3993K->3993K(1056768K)], 0.0161352 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
2021-01-30T15:01:23.406+0800: [Full GC (Ergonomics) [PSYoungGen: 16316K->6000K(18432K)] [ParOldGen: 30303K->40600K(40960K)] 46619K->46601K(59392K), [Metaspace: 3993K->3993K(1056768K)], 0.0113074 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 18432K, used 10909K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 66% used [0x00000000fec00000,0x00000000ff6a7530,0x00000000ffc00000)
from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 40600K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 99% used [0x00000000fc400000,0x00000000feba63c0,0x00000000fec00000)
Metaspace used 4000K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 447K, capacity 460K, committed 512K, reserved 1048576K
############################# -XX:+PrintHeapAtGC ###################################
{Heap before GC invocations=1 (full 0):
PSYoungGen total 18432K, used 16303K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 99% used [0x00000000fec00000,0x00000000ffbebca0,0x00000000ffc00000)
from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 0K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 0% used [0x00000000fc400000,0x00000000fc400000,0x00000000fec00000)
Metaspace used 3996K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 446K, capacity 460K, committed 512K, reserved 1048576K
Heap after GC invocations=1 (full 0):
PSYoungGen total 18432K, used 2016K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ffc00000)
from space 2048K, 98% used [0x00000000ffc00000,0x00000000ffdf8100,0x00000000ffe00000)
to space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
ParOldGen total 40960K, used 12129K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 29% used [0x00000000fc400000,0x00000000fcfd8740,0x00000000fec00000)
Metaspace used 3996K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 446K, capacity 460K, committed 512K, reserved 1048576K
}
{Heap before GC invocations=2 (full 0):
PSYoungGen total 18432K, used 18341K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 99% used [0x00000000fec00000,0x00000000ffbf1538,0x00000000ffc00000)
from space 2048K, 98% used [0x00000000ffc00000,0x00000000ffdf8100,0x00000000ffe00000)
to space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
ParOldGen total 40960K, used 12129K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 29% used [0x00000000fc400000,0x00000000fcfd8740,0x00000000fec00000)
Metaspace used 3998K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 446K, capacity 460K, committed 512K, reserved 1048576K
Heap after GC invocations=2 (full 0):
PSYoungGen total 18432K, used 1996K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ffc00000)
from space 2048K, 97% used [0x00000000ffe00000,0x00000000ffff3100,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 28540K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 69% used [0x00000000fc400000,0x00000000fdfdf180,0x00000000fec00000)
Metaspace used 3998K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 446K, capacity 460K, committed 512K, reserved 1048576K
}
{Heap before GC invocations=3 (full 1):
PSYoungGen total 18432K, used 1996K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ffc00000)
from space 2048K, 97% used [0x00000000ffe00000,0x00000000ffff3100,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 28540K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 69% used [0x00000000fc400000,0x00000000fdfdf180,0x00000000fec00000)
Metaspace used 3998K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 446K, capacity 460K, committed 512K, reserved 1048576K
Heap after GC invocations=3 (full 1):
PSYoungGen total 18432K, used 0K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ffc00000)
from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 30303K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 73% used [0x00000000fc400000,0x00000000fe197cb0,0x00000000fec00000)
Metaspace used 3998K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 446K, capacity 460K, committed 512K, reserved 1048576K
}
{Heap before GC invocations=4 (full 2):
PSYoungGen total 18432K, used 16316K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 99% used [0x00000000fec00000,0x00000000ffbef198,0x00000000ffc00000)
from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 30303K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 73% used [0x00000000fc400000,0x00000000fe197cb0,0x00000000fec00000)
Metaspace used 3998K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 446K, capacity 460K, committed 512K, reserved 1048576K
Heap after GC invocations=4 (full 2):
PSYoungGen total 18432K, used 6000K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 36% used [0x00000000fec00000,0x00000000ff1dc3c0,0x00000000ffc00000)
from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 40600K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 99% used [0x00000000fc400000,0x00000000feba63d8,0x00000000fec00000)
Metaspace used 3998K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 446K, capacity 460K, committed 512K, reserved 1048576K
}
其他参数
其他参数
04-通过Java代码获取JVM参数
/**
*
* 监控我们的应用服务器的堆内存使用情况,设置一些阈值进行报警等处理
*
* @author shkstart
* @create 15:23
*/
public class MemoryMonitor {
public static void main(String[] args) {
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
MemoryUsage usage = memorymbean.getHeapMemoryUsage();
System.out.println("INIT HEAP: " + usage.getInit() / 1024 / 1024 + "m");
System.out.println("MAX HEAP: " + usage.getMax() / 1024 / 1024 + "m");
System.out.println("USE HEAP: " + usage.getUsed() / 1024 / 1024 + "m");
System.out.println("\nFull Information:");
System.out.println("Heap Memory Usage: " + memorymbean.getHeapMemoryUsage());
System.out.println("Non-Heap Memory Usage: " + memorymbean.getNonHeapMemoryUsage());
System.out.println("=======================通过java来获取相关系统状态============================ ");
System.out.println("当前堆内存大小totalMemory " + (int) Runtime.getRuntime().totalMemory() / 1024 / 1024 + "m");// 当前堆内存大小
System.out.println("空闲堆内存大小freeMemory " + (int) Runtime.getRuntime().freeMemory() / 1024 / 1024 + "m");// 空闲堆内存大小
System.out.println("最大可用总堆内存maxMemory " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "m");// 最大可用总堆内存大小
}
}
5、分析GC日志
5.1、GC日志参数
5.2、GC日志格式
复习:GC分类s
1、新生代收集:当Eden区满的时候就会进行新生代收集,所以新生代收集和S0区域和S1区域无关
2、老年代收集和新生代收集的关系:进行老年代收集之前会先进行一次年轻代的垃圾收集,原因如下:一个比较大的对象无法放入新生代,那它自然会往老年代去放,如果老年代也放不下,那会先进行一次新生代的垃圾收集,之后尝试往新生代放,如果还是放不下,才会进行老年代的垃圾收集,之后在往老年代去放,这是一个过程,我来说明一下为什么需要往老年代放,但是放不下,而进行新生代垃圾收集的原因,这是因为新生代垃圾收集比老年代垃圾收集更加简单,这样做可以节省性能
3、进行垃圾收集的时候,堆包含新生代、老年代、元空间/永久代:可以看出Heap后面包含着新生代、老年代、元空间,但是我们设置堆空间大小的时候设置的只是新生代、老年代而已,元空间是分开设置的
4、哪些情况会触发Full GC
:老年代空间不足、方法区空间不足、显示调用System.gc()、Minior GC进入老年代的数据的平均大小 大于 老年代的可用内存、大对象直接进入老年代,而老年代的可用空间不足
不同GC分类的GC细节
/**
* -XX:+PrintCommandLineFlags
*
* -XX:+UseSerialGC:表明新生代使用Serial GC ,同时老年代使用Serial Old GC
*
* -XX:+UseParNewGC:标明新生代使用ParNew GC
*
* -XX:+UseParallelGC:表明新生代使用Parallel GC
* -XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC
* 说明:二者可以相互激活
*
* -XX:+UseConcMarkSweepGC:表明老年代使用CMS GC。同时,年轻代会触发对ParNew 的使用
* @author shkstart
* @create 17:19
*/
public class GCUseTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
while(true){
byte[] arr = new byte[1024 * 10];//10kb
list.add(arr);
// try {
// Thread.sleep(5);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
}
老年代使用CMS GC
GC设置方法:参数中使用-XX:+UseConcMarkSweepGC,说明老年代使用CMS GC,同时年轻代也会触发对ParNew的使用,因此添加该参数之后,新生代使用ParNew GC,而老年代使用CMS GC,整体是并发垃圾收集,主打低延迟
打印出来的GC细节:
新生代使用Serial GC
GC设置方法:参数中使用-XX:+UseSerialGC,说明新生代使用Serial GC,同时老年代也会触发对Serial Old GC的使用,因此添加该参数之后,新生代使用Serial GC,而老年代使用Serial Old GC,整体是串行垃圾收集
打印出来的GC细节:
DefNew代表新生代使用Serial GC,然后Tenured代表老年代使用Serial Old GC
GC日志分类
MinorGC
FullGC
GC日志结构剖析
垃圾收集器
GC前后情况
GC时间
Minor GC 日志解析
上图是对下图的一条GC日志展开来说
Full GC 日志解析
上图是对下图的一条GC日志展开来说
5.3、GC日志分析工具
加上jvm参数后,运行
package com.atguigu.java;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* java.lang.OutOfMemoryError: Metaspace异常演示:
*
* -Xms60m -Xmx60m -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:d:/Metaspace00M.log
*
* @author shkstart
* @create 13:25
*/
public class MetaspaceOOM extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
MetaspaceOOM test = new MetaspaceOOM();
for (int i = 0; i < 10000; i++) {
//创建ClassWriter对象,用于生成类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
//指明版本号,修饰符,类名,包名,父类,接口
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
//返回byte[]
byte[] code = classWriter.toByteArray();
//类的加载
test.defineClass("Class" + i, code, 0, code.length);//Class对象
j++;
}
} finally {
System.out.println(j);
}
}
}
生成文件
加JVM参数,运行
package com.atguigu.java;
import java.util.ArrayList;
/**
* 测试生成详细的日志文件
*
* -Xms60m -Xmx60m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:d:/GCLogTest.log
*
* @author shkstart
* @create 14:27
*/
public class GCLogTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
byte[] arr = new byte[1024 * 50];//50KB
list.add(arr);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
GCEasy
点击选择文件
40M已经是顶峰了,再分配就OOM了
Java8默认用Parallel GC,重点关注吞吐量
对比其他GC,可以改G1、CMS+ParNew,看看这俩是不是主打低延迟,但吞吐量会差一点。
GC之后
GC之前
GC时间
年轻代,GC后占空间减少
老年代,一直涨
GCViewer
基本概述
安装
双击运行
其他工具(了解)
后面这几章内容需要购买课程:https://www.itdachang.com/
6、OOM常见各种场景及解决方案
7、性能优化案例
8、Java代码层及其他层面调优
标签:used,下篇,String,GC,内存,JVM,new,public 来源: https://www.cnblogs.com/chenguanqin/p/16218974.html