【架构师面试-JVM原理-5】-JVM内存泄漏故障排查实战

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

http://localhost:12345/heap

测试非堆内存溢出

为尽快出现内存溢出,需要设置内存参数

http://localhost:12345/noheap

 

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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不要迷恋发哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值