内存爆炸、CPU100%问题定位

一、内存爆炸相关

1、关于-Xms(最小堆内存)和-Xmx(最大堆内存)

最大堆内存为JVM能向操作系统申请的最大内存。最小堆内存并不是程序一启动就申请这么多内存,而是当前进程如果申请的内存已超过最小堆内存,内存回收时,大于最小堆内存的部分会返回给操作系统,其它申请的内存不会。

2、JVM初始化时申请实际物理内存

默认没有配置-XX:+AlwaysPreTouch参数时,JVM进程申请的内存只是虚拟内存,程序运行时根据虚拟内存地址映射到实际的物理内存,缺页时才将数据加载到物理内存,分配实际的物理空间。加上-XX:+AlwaysPreTouch参数后,程序将直接分配到实际的物理内存,而不是根据需要在缺页时才申请物理内存。

简单来说就是没有开启预热时,JVM只是向操作系统做了登记,告诉操作系统我需要多少内存,但实际上并没有进行物理分配,加上该参数后在程序启动时就会分配物理内存。

注意:加上该参数可能会导致程序启动变慢。

3、OutOfMemory问题排查

(1) 堆内存溢出排查

内存溢出示例如下:

/**
 * -Xmx512m -server -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\Users\Administrator\Desktop\java_heapdump.hprof
 */
public class OutOfMemoryExample1 {
    
    

 private static List<Object> space = new ArrayList<>();

 public static void main(String[] args) throws Exception {
    
    
  // 内存泄漏 最终会导致 = 内存溢出
  for (int i = 0; i < 1000; i++) {
    
    
   space.add(new byte[1024 * 1024 * 128]);
   Thread.sleep(3000L);
  }
 }
}

通过MAT(Eclipse Memory Analyzer)工具查找leak suspect即可排查:

在这里插入图片描述

(2) 堆外内存溢出排查

直接内存溢出示例:

/**
 * 堆外内存溢出
 * 控制堆外内存大小:-XX:MaxDirectMemorySize=128m -XX:+HeapDumpOnOutOfMemoryError
 */
public class OutOfMemoryExample2 {
    
    

 private static List<Object> space = new ArrayList<>();

 public static void main(String[] args) throws Exception {
    
    
  // 内存泄漏 最终会导致  内存溢出
  for (int i = 0; i < 1000; i++) {
    
    
   ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 64);
   byteBuffer.put(new byte[1024 * 1024 * 5]);
   space.add(byteBuffer);
   Thread.sleep(2000L);
  }
 }
}

对于堆外内存溢出的情况,通过jvisualvm中的btrace插件分析调用情况,如下:
在这里插入图片描述


二、CPU 100及死锁问题定位

1、CPU 100问题排查

CPU占用率过高通常是因为死循环或者递归调用导致的,通过jstack基本能定位到具体是哪些线程在执行CPU高度密集型操作。示例代码如下:

public class Cpu100Example {
    
    

 public static void main(String[] args) {
    
    
  System.out.println("Starting executing the programme");
  // 如果有多核,开启与逻辑cpu相等的线程数执行计算型任务即可,不要有阻塞或者I/O操作
  while (true) {
    
    
   new Random().nextInt();
  }
 }
}

问题解决思路如下:

(1) 找到程序对应进程号

通过jps或者jcmd命令可查到程序进程号,如下:

[universe@VM_0_13_centos ~]$ jps -l
2792 org.apache.zookeeper.server.quorum.QuorumPeerMain
2745 org.apache.zookeeper.server.quorum.QuorumPeerMain
30858 com.netease.issue.cpu.Cpu100Example
2716 org.apache.zookeeper.server.quorum.QuorumPeerMain
31822 sun.tools.jps.Jps

(2) 查找进程对应的线程编号

top -H -p 30858

结果如下:
在这里插入图片描述
从上图可以看到,pid为30859的线程几乎占用了全部CPU。

(3)查看线程堆栈信息

jstack命令内容中的nid为上一步我们获取到的线程pid的16进制形式,因此先把线程pid转换为16进制,再对jstack内容进行查找。

在这里插入图片描述
现在可以看到该线程的方法调用栈,进而找到相应的代码。

2、死锁问题排查

示例代码如下:

public class DeadLockExample {
    
    

 private static Object monitor1 = new Object();

 private static Object monitor2 = new Object();

 public static void main(String[] args) {
    
    
  new Thread(new ObtainLockTask1()).start();
  new Thread(new ObtainLockTask2()).start();
 }

 private static class ObtainLockTask1 implements Runnable {
    
    

  @Override
  public void run() {
    
    
   System.out.println(Thread.currentThread().getName() + " starts to obtain lock from monitor1");
   try {
    
    
    while (true) {
    
    
     synchronized (monitor1) {
    
    
      System.out.println(Thread.currentThread().getName() + " has obtained lock from monitor1");
      Thread.sleep(3000);
      synchronized (monitor2) {
    
    
       System.out.println(Thread.currentThread().getName() + " starts to obtain lock from monitor2");
      }
     }
    }
   } catch (InterruptedException e) {
    
    
    e.printStackTrace();
   }
  }
 }

 private static class ObtainLockTask2 implements Runnable {
    
    

  @Override
  public void run() {
    
    
   System.out.println(Thread.currentThread().getName() + " starts to obtain lock from monitor2");
   try {
    
    
    while (true) {
    
    
     synchronized (monitor2) {
    
    
      System.out.println(Thread.currentThread().getName() + " has obtained lock from monitor2");
      Thread.sleep(3000);
      synchronized (monitor1) {
    
    
       System.out.println(Thread.currentThread().getName() + " starts to obtain lock from monitor1");
      }
     }
    }
   } catch (InterruptedException e) {
    
    
    e.printStackTrace();
   }
  }
 }
}

依然通过jstack命令查看线程堆栈信息可以定位,如下图:
在这里插入图片描述

注意:通过synchronized关键字导致的Java平台级死锁通过jstack命令可以直接分析出来,而使用ReentrantLock导致的死锁则不能。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lingbomanbu_lyl/article/details/133946040