zoukankan      html  css  js  c++  java
  • Java 虚拟机的内存溢出

    在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生 OutOfMemoryError 异常的可能。

    在Eclipse中进行JVM参数设置

    可以直接通过上方菜单栏的 Run 下的 Run Configurations... 或者 Debug Configurations... 选项进行设置。

    双击 Java Application 会生成一个新的配置卡。

    在新的配置卡中可配置名称Name,Main里面的Project配置项目名,Main class配置main方法所在的类。

    (如果是从main方法里面右键打开的Run As-->Run Configurations...或者Debug As-->Debug Configurations...,这几项会自己生成)

    Arguments下面的VM arguments 里面配置虚拟机参数。

     参数解释: 

    1,堆是存储对象实例的,创建的对象都是在堆中进行内存分配的;设置堆的大小:-Xmx20M(最大值) ,-Xms20M(最小值),其中-Xmn设置年轻代大小。

    2,栈是存储局部变量,操作栈,动态链接,方法出口(都在栈桢中) 的地方,调用方法时,会创建栈桢;设置栈的大小:-Xss128K。

    3,方法区是存放Class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等。此外运行时常量池是属于方法区的,即存放常量,静态常量等;设置方法区大小,-XX:PermSize=10M和-XX:MaxPermSize=10M。

    4,本地直接内存;设置本地直接内存大小:-XX:MaxDirectMemorySize(默认与-Xmx的值一样)。

    堆内存溢出

    Java堆用于存储对象实例,只要不断的创建对象,并且保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

    参数设置:-verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError

    限制Java堆的大小为20M,不可扩展(将堆的最小值 -Xms 参数和最大值 -Xmx 参数设置一样即可避免堆自动扩展),通过参数 -XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时 Dump 出当前的内存堆转储快照以便事后进行分析。

    代码:

    package jvm;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class HeapOOM {
    
        static class OOMObject {
        }
    
        public static void main(String[] args) {
            List<OOMObject> list = new ArrayList<>();
    
            while (true) {
                list.add(new OOMObject());
            }
    
        }
    
    }

    运行结果:

    栈内存溢出

    在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,所以 -Xoss 参数无效,栈容量只由 -Xss 参数控制。

    参数设置:-Xss128k

    代码:

    package jvm;
    
    public class JavaVMStackSOF {
    
        private int stackLength = 1;
        
        public void stackLeak() {
            stackLength++;
            stackLeak();
        }
        
        public static void main(String[] args) throws Throwable{
            JavaVMStackSOF oom = new JavaVMStackSOF();
            try {
                oom.stackLeak();
            } catch (Throwable e) {
                System.out.println("stack length:" + oom.stackLength);
                throw e;
            }
            
        }
    
    }

    运行结果:

    方法区内存溢出

    参数设置:-XX:PermSize=10M -XX:MaxPermSize=10M

    注意:JDK8中用 MetaspaceSize 代替 PermSize,因此将 -XX:PermSize=10M -XX:MaxPermSize=10M 修改为 -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M

    String.intern() 方法是一个Native方法,如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

    代码一:

    package jvm;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class RuntimeConstantPoolOOM {
    
        public static void main(String[] args) {
            // 使用List保持常量池引用,避免 Full GC 回收常量池行为
            List<String> list = new ArrayList<>();
            // 10MB的 PermSize在Integer范围内足够产生OOM了
            int i = 0;
            while (true) {
                list.add(String.valueOf(i++).intern());
            }
    
        }
    
    }

    以上代码在JDK1.6及之前的版本下,由于常量池分配在永久带内,运行时常量池溢出,使用JDK1.7以及更高版本运行这段程序不会得到相同结果,while循环将一直进行下去。因为1.7及更高版本的常量池值只保存了引用。

    代码二:

    package jvm;
    
    public class RuntimeConstantPoolOOM {
    
        public static void main(String[] args) {
            
            String str1 = new StringBuilder("计算机").append("软件").toString();
            System.out.println(str1.intern() == str1);
            
            String str2 = new StringBuilder("ja").append("va").toString();
            System.out.println(str2.intern() == str2);
    
        }
    
    }

    运行结果:

    结果分析:

    JDK1.6会得到两个false。

    在JDK1.6中,intern() 方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久带中这个字符串实例的引用。而 StringBuilder 创建的字符串实例在堆上,所以不是同一个引用,返回 false。

    JDK1.7中会得到一个true和一个false。

    在JDK1.7中,intern() 方法不会再复制实例,只在常量池中记录首次出现的实例引用,该引用指向的是堆中的实例,这个实例就是 StringBuilder 创建的那个字符串实例,所以是同一个引用,返回true。

    返回false是因为 “java” 这个字符串在中StringBuilder之前已经出现过,字符串常量池中已经有它的引用了,而 StringBuilder 又重新创建了一个“java”字符串实例,所以str2中保存的是新创建的实例的引用,而str2.intern() 返回的是旧的实例的引用,所以不是同一个引用,返回false。

    代码三:

    package jvm;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class JavaMethodAreaOOM {
    
        public static void main(String[] args) {
    
            while (true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOmObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                        return proxy.invokeSuper(obj, args);
                    }
                });
    
                enhancer.create();
    
            }
    
        }
    
        static class OOmObject {
        }
    
    }

    以上代码使用使用CGLib使方法区出现内存溢出异常。使用到下面的两个jar包。

    JDK8运行结果:

    本机直接内存溢出

    参数设置:-Xmx20M  -XX:MaxDirectMemorySize=10M

    直接内存容量可通过 MaxDirectMemorySize 指定,如果不指定,则默认和Java堆最大值一样(-Xmx)。

    代码:

    package jvm;
    
    import java.lang.reflect.Field;
    
    import sun.misc.Unsafe;
    
    public class DirectMemoryOOM {
    
        private static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) throws Exception {
            //通过反射获取实例
            Field unsafeField = Unsafe.class.getDeclaredFields()[0];
            unsafeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeField.get(null);
            while (true) {
                //申请分配内存
                unsafe.allocateMemory(_1MB);
            }
        }
    
    }

    运行结果:

  • 相关阅读:
    [北京.NET俱乐部]征集T恤设计方案
    [新功能]个人Blog首页分页浏览
    [公告]关于用户资料的保密
    奇怪的邮件与MSN密码
    文章发布功能改动
    [活动]北京.NET俱乐部首次活动照片及讲课资料
    [活动公告]上海.NET俱乐部首次活动预告
    [征询意见]关于开设.NET 2.0专题
    [北京.NET俱乐部活动]参加者签名并谈一下感受
    CSS3 transform 2D实验(1)
  • 原文地址:https://www.cnblogs.com/wbxk/p/7111558.html
Copyright © 2011-2022 走看看