1: jmap+MAT实战内存溢出
1:演示堆内存和非堆内存溢出
jvm/user.java
package openclass.jvm;
public class User {
private int id;
private String name;
public User(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;
}
}
jvm/MemoryController.java
package openclass.jvm;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MemoryController {
private List<User> userList = new ArrayList<User>();
private List<Class<?>> classList = new ArrayList<Class<?>>();
/**
* -Xmx32M -Xms32M
* */
@GetMapping("/heap")
public String heap() {
int i=0;
// 模拟堆内存溢出,不断向userList中放User对象
while(true) {
userList.add(new User(i++, UUID.randomUUID().toString()));
}
}
/**
* -XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M
* */
@GetMapping("/nonheap")
public String nonheap() {
while(true) {
// 模拟非堆内存溢出,不断向userList中放User对象
classList.addAll(Metaspace.createClasses());
}
}
}
测试堆内存溢出
为尽快出现内存溢出,需要设置内存参数
-XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M
启动MonitorTuningApplication.java
测试非堆内存溢出
为尽快出现内存溢出,需要设置内存参数
2:导出内存映象文件
使用jmap命令导出
启动MonitorTuningApplication,让其出现OOM
在cmd中使用jps -l ,查看pid
切换到桌面
cd desktop
导出内存映象文件
jmap -dump:format=b,file=heap.hprof 18224
3: 使用MAT分析内存溢出
直接打开MemoryAnalyzer.exe
选File-open file,找到导出的heap.hprof
分析内存:User对象有103312个
查看那些引用了User对象,只看强引用
2:JVisualVM的可视化监控
VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的).
VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。
内存信息
线程信息
Dump堆(本地进程)
Dump线程(本地进程)
打开堆Dump。堆Dump可以用jmap来生成。
打开线程Dump
生成应用快照(包含内存信息、线程信息等等)
性能分析。CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类对象占用的内存,检查哪些类占用内存多)
打开jvisualvm.exe
界面如下,监控本地Java进程不需要做任何设置。
从界面上看还是比较简洁的,左边是树形结构,自动显示当前本机所运行的Java程序,还可以添加远程的Java VM,其中括号里面的PID指的是进程ID。OverView界面显示VM启动参数以及该VM对应的一些属性。Monitor界面则是监控Java堆大小,Permgen大小,Classes和线程数量。
3: jstack实战死循环与死锁
1:线程状态
线程的生命周期:一个线程从它创建到启动,然后运行,直到最后执行完的整个过程。
新建状态:即创建一个新的线程对象,注意新创建的线程对象如果没有调用start()方法将永远得不到运行。
就绪状态:当新的线程对象调用start()方法时,就进入了就绪状态,进入就绪状态的线程不一定立即就开始运行。
运行状态:进入运行状态的线程,会由CPU处理其线程体中的代码。
阻塞状态:运行状态的线程有可能出现意外情况而中断运行,比如进行IO操作,内存的读写,等待键盘输入数据(注意不是出错,出错将提前终止 线程)而进入阻塞状态。当阻塞条件解除后,线程会恢复运行。但其不是立即进入运行状态,而是进入就绪状态。
终止状态:当线程中run()方法语句执行完后进入终止状态。
2:线程状态之间的转换
同步的实现方法有五种,分别是synchronized、wait与notify、sleep、suspend、join
synchronized: 一直持有锁,直至执行结束
wait():使一个线程处于等待状态,并且释放所持有的对象的lock,需捕获异常。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,需捕获异常,不释放锁。
sleep()方法是Thread类中方法,而wait()方法是Object类中的方法。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态,在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
3:死锁与死循环
jvm/CpuController.java
/**
* 死循环
* */
@RequestMapping("/loop")
public List<Long> loop(){
String data = "{\"data\":[{\"partnerid\":]";
return getPartneridsFromJson(data);
}
private Object lock1 = new Object();
private Object lock2 = new Object();
/**
* 死锁
* */
@RequestMapping("/deadlock")
public String deadlock(){
new Thread(()->{
synchronized(lock1) {
try {Thread.sleep(1000);}catch(Exception e) {}
synchronized(lock2) {
System.out.println("Thread1 over");
}
}
}) .start();
new Thread(()->{
synchronized(lock2) {
try {Thread.sleep(1000);}catch(Exception e) {}
synchronized(lock1) {
System.out.println("Thread2 over");
}
}
}) .start();
return "deadlock";
}
测试
在cmd下进入项目的目录(先要改pom.xml中,将war改成jar)
使用maven打包
在ideal中找到jar包,上传到虚拟机任一目录
http://192.168.246.128:12345/loop
因为是死循环,程序不会结束。
使用top命令查看cpu负载
在开一个窗口,访问死循环
发现cpu已经接近100%,此时会导致其它请求无法访问。
4:使用jstack定位死循环
2988是死循环的进程pid
jstack 2988 > 2988.txt
下载2988.txt
sz 2988.txt
查盾2988.txt
可以看到线程正处理RUNNABLE,调用getPartneridsFromJson方法出现问题。
5:使用jstack定位死锁
http://192.168.246.128:12345/deadlock
jstack 2988 >2988.txt
sz 2988.txt
查看2988.txt