目录
- 一、内存爆炸相关
- 1、关于-Xms(最小堆内存)和-Xmx(最大堆内存)
- 2、JVM初始化时申请实际物理内存
- 3、OutOfMemory问题排查
- (1) 堆内存溢出排查
- (2) 堆外内存溢出排查
- 二、CPU 100及死锁问题定位
- 1、CPU 100问题排查
- (1) 找到程序对应进程号
- (2) 查找进程对应的线程编号
- (3)查看线程堆栈信息
- 2、死锁问题排查
一、内存爆炸相关
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
导致的死锁则不能。