image

上面这幅图就是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.StackOverflowError
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.StackOverflowError
10472
....

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

result:

975998
java.lang.StackOverflowError
....

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)
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐