jvm常见内存溢出异常

时间:2022-04-27 16:21:35

jvm常见内存溢出异常

上面这幅图就是jvm虚拟机运行时的主要数据区,蓝色部分是线程共享区域,而白色部分就是线程私有区域。

以下例子均在jdk1.7中运行

1.堆内存溢出

/**
* VM Args: -Xms2m -Xmx2m
* Created by Stay on 2017/5/15 14:50.
*/

public class Base1 {

public static void main(String[] args) {
List<Object> list = new ArrayList<>();
while(true){
list.add(new byte[2 * 1024]);
}
}
}

result: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

-Xms2m 堆内存最小为2m

-Xmx2m 堆内存最大为2m,参数设置一样是为了避免堆自动扩展

我们再加上-XX:+PrintGCDetails 打印出GC回收的过程:

[GC [PSYoungGen: 2047K->504K(2560K)] 2047K->2028K(5120K), 0.0014692 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 1524K->1696K(5120K)] 2028K->1696K(7680K) [PSPermGen: 2776K->2775K(21504K)], 0.0123204 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 2046K->512K(2560K)] 3743K->4080K(7680K), 0.0009728 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 3568K->3587K(5120K)] 4080K->3587K(7680K) [PSPermGen: 2792K->2792K(21504K)], 0.0063521 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 2047K->503K(2560K)] [ParOldGen: 3587K->5066K(5120K)] 5635K->5570K(7680K) [PSPermGen: 2815K->2815K(21504K)], 0.0091229 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 2046K->2023K(2560K)] [ParOldGen: 5066K->5056K(5120K)] 7113K->7079K(7680K) [PSPermGen: 2843K->2843K(21504K)], 0.0054760 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 2046K->2046K(2560K)] [ParOldGen: 5056K->5056K(5120K)] 7102K->7102K(7680K) [PSPermGen: 2843K->2843K(21504K)], 0.0056718 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 2048K->2046K(2560K)] [ParOldGen: 5118K->5118K(5120K)] 7166K->7164K(7680K) [PSPermGen: 2845K->2845K(21504K)], 0.0051912 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 2046K->2046K(2560K)] [ParOldGen: 5118K->5106K(5120K)] 7164K->7153K(7680K) [PSPermGen: 2845K->2845K(21504K)], 0.0075618 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 2046K->2046K(2560K)] [ParOldGen: 5119K->5119K(5120K)] 7165K->7165K(7680K) [PSPermGen: 2846K->2846K(21504K)], 0.0056501 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 2046K->2046K(2560K)] [ParOldGen: 5119K->5119K(5120K)] 7165K->7165K(7680K) [PSPermGen: 2846K->2846K(21504K)], 0.0051502 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GCException in thread "main" [PSYoungGen: 2048K->0K(2560K)] [ParOldGen: 5119K->518K(4608K)] 7167K->518K(7168K) [PSPermGen: 2872K->2872K(21504K)], 0.0084184 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space
at jvm.base1.main(base1.java:16)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Heap
PSYoungGen total 2560K, used 67K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 3% used [0x00000000ffd00000,0x00000000ffd10c38,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 4608K, used 518K [0x00000000ff800000, 0x00000000ffc80000, 0x00000000ffd00000)
object space 4608K, 11% used [0x00000000ff800000,0x00000000ff8819e0,0x00000000ffc80000)
PSPermGen total 21504K, used 2897K [0x00000000fa600000, 0x00000000fbb00000, 0x00000000ff800000)
object space 21504K, 13% used [0x00000000fa600000,0x00000000fa8d4468,0x00000000fbb00000)

从打印的这些信息就能看出,虽然JVM在尽力的回收,但是也抵挡不住while(true)的威力。

二.栈溢出

/**
* VM Args: -Xss128k
* Created by Stay on 2017/5/15 15:41.
*/

public class Base2 {

private int stackLength = 1;

public static void main(String[] args) {
Base2 base2 = null;
try {
base2 = new Base2();
base2.stackLeak();

} catch (Throwable e) {
System.out.println("stack length:" + base2.stackLength);
throw e;
}
}
public void stackLeak() {
stackLength++;
stackLeak();
}
}

result:

Exception in thread "main" java.lang.*Error
stack length:11409
.....

Thread api中有一个不常用的构造器 Thread(ThreadGroup group, Runnable target, String name, long stackSize) ,最后一个参数就是每个线程的栈大小的设置

再来个栗子:

/**
* Created by Stay on 2017/5/15 16:15.
*/

public class Base3 {
private static int count = 1;
public static void main(String[] args) {
Thread t1 = new Thread(null,new Runnable() {
@Override
public void run()
{
try {
add(1);
} catch (Throwable e) {
System.out.println(count);
e.printStackTrace();
}
}
private void add(int i) {
count++;
add(i + 1);
}
},"stackTest");
t1.start();
}
}

result:

java.lang.*Error
10472
....

在最后加上一个stackSize参数 Thread t1 = new Thread(null,....,"stackTest",1 << 24);

result:

975998
java.lang.*Error
....

count最后的值比刚开始大了不少,就是因为我们给每个线程分配的栈空间变大了,但是能创建的线程就变小了,因为总的栈空间没变。这个构造方法一般用的很少。

有些时候我们把jvm只分为两块 堆和栈,这样的分法是比较粗糙的,只能说明大多数程序员最关注的,就是这两块区域,这里所指的栈就是虚拟机栈,或者说是虚拟机中局部变量表部分.

而堆 就是存放对象实例,所有new出来的实例都存放在这里 栈中只是引用堆中的对象。垃圾回收器也是主要管理这个区域,称为GC堆

三.方法区内存溢出(PermGen space)

我们利用字符串的intern()方法进行实验

为了尽快溢出,jvm参数设置永久代参数5M: -XX:PermSize=5m -XX:MaxPermSize=5m

public static void main(String[] args) {
List list = new ArrayList();
int i = 0;
while(true){
list.add((String.valueOf(i++)).intern());
}
}

运行结果如下:OutOfMemoryError后面提示 PermGen space ;改代码在jdk6及以下版本能够报出如下异常,jdk7高版本及以上部分jvm已经逐渐开始取消永久代的原因,只会报出堆内存异常

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.TestSocket.main(TestSocket.java:12)