import java.util.LinkedList;
import java.util.List;
public class OutOfMemoryDump {
/**
* JVM 参数
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Volumes/mac
*/
public static void main(String[] args) {
List<Byte[]> bytes = new LinkedList<>();
while(true){
Byte[] byteNew = new Byte[1024];
bytes.add(byteNew);
}
}
}
运行时添加虚拟机运行参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Volumes/mac/oomdump.dump
然后可以在/Volumes/mac/中看到有个oomdump.dump的文件。
jvisualvm
会启动一个界面,如下
这个界面可以查看到当前已经在运行的jvm进程。点击左上角载入,然后选择我们的dump文件。
可以看到概要信息,我们出现的错误是OOM,线程是main主线程,然后点“类”按钮,会显示出现OOM时的类的信息:
可以很明显的看到有个类占据了95.9%的空间,是java.lang.Byte
类,回看我们的代码,在List中不断增加大小为1024的Byte数组,那么这个地方就是引起内存溢出的主要地方。
public class DeadLock {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() ->{
synchronized (lock1){
System.out.println("第一条线程开始运行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println("第一条线程开始结束");
}
}
}).start();
new Thread(() ->{
synchronized (lock2){
System.out.println("第二条线程开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println("第二条线程结束");
}
}
}).start();
}
}
第一条线程持有锁lock1,尝试去获取锁lock2,第二条线程持有锁lock2,尝试去获取第一条线程的锁lock1,两条线程都持有自己的锁,想获取对方的锁,造成死锁。
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007fcce103bca8 (object 0x000000076af74480, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007fcce1037c08 (object 0x000000076af74490, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at net.blf2.testmac.DeadLock.lambda$main$1(DeadLock.java:35)
- waiting to lock <0x000000076af74480> (a java.lang.Object)
- locked <0x000000076af74490> (a java.lang.Object)
at net.blf2.testmac.DeadLock$$Lambda$2/1508395126.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at net.blf2.testmac.DeadLock.lambda$main$0(DeadLock.java:20)
- waiting to lock <0x000000076af74490> (a java.lang.Object)
- locked <0x000000076af74480> (a java.lang.Object)
at net.blf2.testmac.DeadLock$$Lambda$1/2136344592.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
提示发现死锁,继续阅读发现Thread-1持有锁0x000000076af74490,等待锁0x000000076af74480,Thread-0持有锁0x000000076af74480,等待锁0x000000076af74490,至此发现死锁并定位到死锁位置,即at net.blf2.testmac.DeadLock.lambda$main 1 ( D e a d L o c k . j a v a : 35 ) 和 a t n e t . b l f 2. t e s t m a c . D e a d L o c k . l a m b d a 1(DeadLock.java:35)和at net.blf2.testmac.DeadLock.lambda 1(DeadLock.java:35)和atnet.blf2.testmac.DeadLock.lambdamain$0(DeadLock.java:20)就是死锁发生的位置。
若是在服务器环境下,并没有图形界面,可以使用jvisualvm的远程(在左侧侧边栏里面可以找到)功能,添加远程jvm实时分析,也可以使用命令:
# jps 用来查看当前机器运行的jvm进程情况,执行后类似如下显示
# 689 RemoteMavenServer
# 1105 Launcher
# 674
# 1124 Jps
# 1063 Main
jps
# 然后使用命令jstack 进程id > 路径 导出线程快照
jstack 1063 > /home/Main.txt
# 把Main.txt 下载到本地进行分析即可
在运行内存溢出那段代码的时候,GC的日志会在控制台输出:
输出日志如下:
Connected to the target VM, address: '127.0.0.1:50139', transport: 'socket'
[GC (Allocation Failure) [PSYoungGen: 2048K->496K(2560K)] 2048K->665K(9728K), 0.0021886 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2542K->496K(2560K)] 2712K->1669K(9728K), 0.0026511 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2541K->512K(2560K)] 3714K->3717K(9728K), 0.0020411 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2557K->480K(2560K)] 5762K->5748K(9728K), 0.0019036 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 5268K->5518K(7168K)] 5748K->5518K(9728K), [Metaspace: 3552K->3552K(1056768K)], 0.0078083 secs] [Times: user=0.04 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 2045K->508K(2560K)] [ParOldGen: 5518K->6960K(7168K)] 7564K->7469K(9728K), [Metaspace: 3552K->3552K(1056768K)], 0.0087421 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 2044K->2007K(2560K)] [ParOldGen: 6960K->6950K(7168K)] 9005K->8957K(9728K), [Metaspace: 3553K->3553K(1056768K)], 0.0110771 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[Full GC (Ergonomics) [PSYoungGen: 2048K->2047K(2560K)] [ParOldGen: 6950K->6950K(7168K)] 8998K->8998K(9728K), [Metaspace: 3553K->3553K(1056768K)], 0.0092453 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7083K->7083K(7168K)] 9131K->9131K(9728K), [Metaspace: 3555K->3555K(1056768K)], 0.0070404 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7164K->7164K(7168K)] 9212K->9212K(9728K), [Metaspace: 3558K->3558K(1056768K)], 0.0049998 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7164K->7146K(7168K)] 9212K->9194K(9728K), [Metaspace: 3558K->3558K(1056768K)], 0.0103918 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7166K->7166K(7168K)] 9214K->9214K(9728K), [Metaspace: 3558K->3558K(1056768K)], 0.0053740 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7166K->7166K(7168K)] 9214K->9214K(9728K), [Metaspace: 3558K->3558K(1056768K)], 0.0049872 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Volumes/mac/OOM.dump ...
Heap dump file created [19048922 bytes in 0.061 secs]
Exception in thread "main" [Full GC (Ergonomics) [PSYoungGen: 2047K->0K(2560K)] [ParOldGen: 7167K->599K(7168K)] 9215K->599K(9728K), [Metaspace: 3562K->3562K(1056768K)], 0.0042563 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
at net.blf2.testmac.OutOfMemoryDump.main(OutOfMemoryDump.java:18)
Heap
PSYoungGen total 2560K, used 86K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 2048K, 4% used [0x00000007bfd00000,0x00000007bfd15850,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
ParOldGen total 7168K, used 599K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
object space 7168K, 8% used [0x00000007bf600000,0x00000007bf695ed0,0x00000007bfd00000)
Metaspace used 3591K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 397K, capacity 400K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: '127.0.0.1:50139', transport: 'socket'
Process finished with exit code 1
[GC (Allocation Failure) [PSYoungGen: 2048K->496K(2560K)] 2048K->665K(9728K), 0.0021886 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
这是一条Minor GC日志或者说是Young GC,PSYoungGen中的PS是垃圾收集器,2048K->496K(2560K)是从2048K收集完成后变成了496K,总的容量是2560K,然后2048K->665K(9728K)这一句是计算上了老年代的空间,从496K变成了665K说明新生代中有些存活的对象进入到了老年代,然后0.0021886 secs是收集时间,看起来延迟还是比较低的,大概2毫秒。
[Full GC (Ergonomics) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 5268K->5518K(7168K)] 5748K->5518K(9728K), [Metaspace: 3552K->3552K(1056768K)], 0.0078083 secs] [Times: user=0.04 sys=0.00, real=0.00 secs]
这是一条Major GC日志或者说是Full GC,会先进行一次Minor GC(Young GC),然后进行老年代的GC,ParOldGen中的Par是垃圾收集器,信息和Young GC的差不多,都是收集前后大小和容量,Metaspace: 3552K->3552K(1056768K)是指元空间的手机信息,元空间是JDK8移除了永久代,在本地内存开辟的一块区域,用来代替永久代的一个区域,主要存放类加载的信息,和类加载器绑定,某个类加载器被回收的话,相应的元空间也会被回收,元空间会动态调整大小。
[Full GC (Allocation Failure) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7166K->7166K(7168K)] 9214K->9214K(9728K), [Metaspace: 3558K->3558K(1056768K)], 0.0049872 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
发现新生代,老年代都没法继续回收了,但程序还在申请内存,于是抛出了OOM异常。