深入理解JVM虚拟机(二):JDK 内存类的异常分析

JVM数据存储

  1. 堆存储(Heap):对象存储,实际上就是JAVA的数据存储
  2. 方法堆栈(Method Stack):存储方法调用的关系。
  3. 永久代(Perm):在JDK1.6及之前,常量数据存储于此区域

异常示例

 

堆存储异常

package com.wlzjdm.jvm.learning;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * VM args : -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * 模拟内存溢出情况,抛出:java.lang.OutOfMemoryError: Java heap space
 * 当配置为:[-Xms20m -Xmx20m]时,执行信息:Heap dump file created [27931458 bytes in 0.604 secs],内存中存放了:810325个对象
 * 当配置为:[-Xms10m -Xmx10m]时,执行信息:Heap dump file created [13075518 bytes in 0.261 secs],内存中存放了:360145个对象
 * 分析.hprof工具为:MemoryAnalyzer
 * 结论:
 * <li>
 * 通过配置-Xms参数设置堆最小值,通过-Xmx设置堆最大值,当堆使用大于最大值得时候就会抛出OutOfMemoryError。
 * </li>
 * <li>
 * 堆是用于存储内存对象的,当我们写代码的时候,生成的每一个对象都会放在堆中。
 * </li>
 * @author FDD
 *
 */
public class HeapOOM {
    static class HeapObject implements Serializable{
        
    }
    
    public static void main(String[] args) throws Exception {
        List<HeapObject> list = new ArrayList<HeapObject>();
        try {
            //计算一个对象大小。按照序列化方式计算
//            ByteArrayOutputStream bos = new ByteArrayOutputStream();
//            ObjectOutputStream oos = new ObjectOutputStream(bos);
//            oos.writeObject(new HeapObject());
//            System.out.println(bos.toByteArray().length);
//            oos.close();
//            bos.close();
            while(true){
                list.add(new HeapObject());
            }
        } catch (Throwable e) {
            System.out.println("List size is :" + list.size());
            e.printStackTrace();
        }
    }
}

执行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid44464.hprof ...
Heap dump file created [27864823 bytes in 0.523 secs]
List size is :810325
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2245)
    at java.util.Arrays.copyOf(Arrays.java:2219)
    at java.util.ArrayList.grow(ArrayList.java:213)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:187)
    at java.util.ArrayList.add(ArrayList.java:411)
    at com.wlzjdm.jvm.learning.HeapOOM.main(HeapOOM.java:42)

方法栈溢出

package com.wlzjdm.jvm.learning;

/**
 * VM Args: -Xss128K
 * 模拟测试本地方法栈溢出情况,抛出:java.lang.StackOverflowError<br>
 * <ul>
 * 配置为:-Xss1K时,stack大小为19015
 * </ul>
 * <ul>
 * 配置为:-Xss2K时,stack大小为18434
 * </ul>
 * <ul>
 * 配置为:-Xss128K时,stack大小为987
 * </ul>
 * <ul>
 * 配置为:-Xss512K时,stack大小为8486
 * </ul>
 * <ul>
 * 配置为:-Xss1M时,      stack大小为19264
 * </ul>
 * <ul>
 * 配置为:-Xss20M时,   stack大小为1270590
 * </ul>
 * <b>结论:</b>
 * <li>
 * -Xss用于配置本地方法栈大小
 * </li>
 * <li>
 * 栈是方法区使用的,很容易理解,一般一个方法都是后进先出的。
 * </li>
 * @author FDD
 *
 */
public class JavaVMStackESOF {
    private int stackLength = 1;
    
    public void stackLeak(){
        stackLength ++;
        stackLeak();
    }
    
    public static void main(String[] args) throws Throwable {
        JavaVMStackESOF javaVMStackESOF = new JavaVMStackESOF();
        try {
            javaVMStackESOF.stackLeak();
        } catch (Throwable e) {
            System.out.println("Stack Length is :" + javaVMStackESOF.stackLength);
            throw e;
        }
    }
}

执行结果:

Stack Length is :11429
Exception in thread "main" java.lang.StackOverflowError
    at com.wlzjdm.jvm.learning.JavaVMStackESOF.stackLeak(JavaVMStackESOF.java:38)
    at com.wlzjdm.jvm.learning.JavaVMStackESOF.stackLeak(JavaVMStackESOF.java:39)
    at com.wlzjdm.jvm.learning.JavaVMStackESOF.stackLeak(JavaVMStackESOF.java:39)
    at com.wlzjdm.jvm.learning.JavaVMStackESOF.stackLeak(JavaVMStackESOF.java:39)
    at com.wlzjdm.jvm.learning.JavaVMStackESOF.stackLeak(JavaVMStackESOF.java:39)
………………

永久代溢出

package com.wlzjdm.jvm.learning;

import java.util.ArrayList;
import java.util.List;

/**
 * VM Args: -XX:PermSize=1M -XX:MaxPermSize=1M <br>
 * 说明:此项是配置永久代大小,在JDK1.6及之前,常量是存放于永久代中,所以限制永久代大小就简介限制常量池大小<br>
 * <code>
 *  <b>JDK1.6 Run Result :</b>
 *  <li> 
 *      Configuration -XX:PermSize=10M -XX:MaxPermSize=10M : 116804 Exception in thread "main" java.lang.OutOfMemoryError: PermGen space <br>
 *  </li>
 *  <li> 
 *      Configuration -XX:PermSize=20M -XX:MaxPermSize=20M : 280572 Exception in thread "main" java.lang.OutOfMemoryError: PermGen space <br>
 *  </li>
 *  <b>JDK > 1.7 Run Result :</b>
 *  <li> 
 *      可以一直运行下去
 *  </li>
 *  <b>结论:-XX:PermSize和-XX:MaxPermSzie是配置永久代的大小,在JDK1.6及之前,常量是存放于永久代中,所以可以通过配置永久代大小配置敞亮池,而在JDK1.6之后,逐步剔除永久代的事情。</b>
 * </code>
 * @author FDD
 *
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) throws Throwable {
        List<String> list = new ArrayList<String>();
        
        int i = 0;
        
        try {
            while(true){
                list.add(String.valueOf(i++).intern());
            }
        } catch (Throwable e) {
            System.out.println(list.size());
            throw e;
        }
    }
}

运行结果:

280572
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at com.wlzjdm.jvm.learning.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:34)

方法区溢出

package com.wlzjdm.jvm.learning;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * VM Args : -XX:PermSize=10M -XX:MaxPermSize=10M <br>
 * 此例用来模拟方法区异常,
 * 结果:OutOfMemoryError: PermGen space
 * @author FDD
 *
 */
public class JavaMethodAreaOOM {
    public static void main(String[] args) throws Exception {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {

                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invoke(obj, args);
                }
            });
            enhancer.create();
        }
    }

    static class OOMObject {
        public OOMObject() {
        }
    }
}

运行结果:

Exception in thread "main" net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:256)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:378)
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:286)
    at com.wlzjdm.jvm.learning.JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:29)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:395)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237)
    ... 3 more
Caused by: java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    ... 8 more

本机内存溢出

package com.wlzjdm.jvm.learning;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

/**
 * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M</br>
 * JVM可以通过指定-XX:MaxDirectMemorySize指定,如果不指定,那么默认与JAVA堆最大值-Xmx一样。<br>
 * 执行结果:Exception in thread "main" java.lang.OutOfMemoryError
 * @author FDD
 *
 */
public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;
    
    public static void main(String[] args) throws Exception {
        Field unsaveField = Unsafe.class.getDeclaredFields()[0];
        unsaveField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsaveField.get(null);
        while(true){
            unsafe.allocateMemory(_1MB);
        }
    }

}

运行结果

Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at com.wlzjdm.jvm.learning.DirectMemoryOOM.main(DirectMemoryOOM.java:22)

总结

对于一个内存溢出的问题,我们一定要搞清楚是那块儿内存溢出,只有明白那块内存溢出,才知道系统的数据存储分配,然后通过分析Heap Dump文件得出具体的类,根据错误类型定位到具体的问题点(注意:DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常)。

原文地址:https://www.cnblogs.com/wlzjdm/p/6876974.html