Java内存溢出实例总结

Java 专栏收录该内容
9 篇文章 0 订阅

java虚拟机规范规定的java虚拟机内存其实就是java虚拟机运行时数据区,其架构如下:


<img width="492" height="325" src="file:///C:/Users/zpy/AppData/Local/Temp/msohtml1/01/clip_image001.jpg" <="" span="">' v:shapes="_x0000_i1029">

其中方法区和堆是由所有线程共享的数据区。

Java虚拟机栈,本地方法栈和程序计数器是线程隔离的数据区。

Java官方定义:http://www.98ki.com/servlet/HomeServlet?method=get&id=53

Java各内存区域分析:http://www.98ki.com/servlet/HomeServlet?method=get&id=43

通过分析各个区域的内容我们分别写出各个区域的内存溢出实例

堆溢出

由Java的官方文档我们可以看出,Java堆中存放:对象、数组。下面以不断创建对象为例:

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

 

public class HeapLeak {

    public static void main(String[] args){

        ArrayList list = newArrayList();

        while(true){

            list.add(new HeapLeak.method());

        }

    }

    static class method{

    }

}

 

栈溢出

从Java官方API中我们知道,栈中存储:基本数据类型,对象引用,方法等。下面以无限递归创建方法和申请栈空间为例,分别演示栈的stackOverflow和OutOfMemory

 

l    Exception in thread "main" java.lang.StackOverflowError

packageMemory;

 

public class StackLeak {

    public static void main(String[] args){

        method();

    }

    public static void method(){

        method();

    }

}

 

l     Exception in thread "main"java.lang.OutOfMemoryError: unable to create new native thread

packageMemory;

 

public class StackOutOfMemory {

    public static int count = 1;

    public  void noStop() {

        while (true) {

        }

    }

 

    public  void newThread() {

        while (true) {

            Thread t = new Thread(new Runnable() {

                publicvoid run() {

                    System.out.println("已创建第"+count+++"个线程");

                    noStop();

                }

            });

            t.start();

        }

 

    }

    public static void main(String[] args){

        new StackOutOfMemory().newThread();

       

    }

}

 

Java hotspot虚拟机中一个线程占用内存可通过-Xss设置,而能创建的线程数计算方法为:

可创建线程数=(物理内存-Os预留内存-堆内存-方法区内存)/单个线程大小

在测试的时候这里还有点小插曲,电脑强关了一次,因为把-Xss设置成了2M,内存使用增加到97%左右,操作系统死了,这个进程不断在创建线程,但是并没有因为内存不足而停下来,直到电脑完全死掉也没有报出错误信息。最后分析是因为电脑空闲内存还有600M,在线程还没有创建完的时候,已经开启的线程太多,在死之前大概能开到200多个,对内存大量消耗,造成系统挂掉。


这里又出现一个有趣的现象,当线程顺序创建到第88个的时候,count跳了很多,并且开始无序,有兴趣的可以深入学习一下线程方面的问题,我也会在后面的博客分析这个问题。


而换成200M的时候,创建第二个线程的时候就报了OutOfMemory.不管Xss设置多少,报错之后,程序都会一直走下去,执行已开线程中的任务。


常量池溢出

从Java官方API中我们知道,常量区代表运行时每个class文件中的常量表。它包括几种常量:编译期的数字常量、方法或者域的引用(在运行时解析)。runtime constant pool的功能类似于传统编程语言的符号表,尽管它包含的数据比典型的符号表要丰富的多。

下面以不断添加Stirng为例:

Exception in thread "main"java.lang.OutOfMemoryError: PermGen space

常量池在方法区中,首先设置持久代大小,使其不可扩展。


然后需要做的就不停地往方法区中加字符串。其中intern()就是查看方法区中有没有这个字符串,没有的话就加进去,如果这里不用intern(),字符串是存在堆里的,会报heapOutOfMemory.

这里需要注意的是,在HotSpot中,方法区是在堆的持久代中的。

packageMemory;

 

importjava.util.ArrayList;

 

public class ConstantPoolLeak {

    public static void main(String[] args) {

        int count = 0;

        ArrayList list = newArrayList();

        while (true)

            list.add(String.valueOf(count++).intern());

    }

}

方法区溢出

从Java官方API中我们知道,方法区存放每个Class的结构,比如说运行时常量池、域、方法数据、方法体、构造函数、包括类中的专用方法、实例初始化、接口初始化。

Java的反射和动态代理可以动态产生Class,另外第三方的CGLIB可以直接操作字节码,也可以动态产生Class,下面通过CGLIB来演示。

importjava.lang.reflect.Method;

 

public class MethodAreaLeak {

 

 

        public static void main(String[] args){

        while(true){

        Enhancer enhancer =new Enhancer();

        enhancer.setSuperClass(OOMObject.class);

        enhancer.setUseCache(false);

        enhancer.setCallback(newMethodInterceptor(){

        public Object intercept(Object obj, Method method,Object[] args,

                          MethodProxy proxy)throws Throwable{

        return proxy.invokeSuper(obj, args);

    }

    });

    enhancer.create();

    }

    }

    class OOMObject{

    }

    }

本机直接内存溢出

Java虚拟机可以通过参数-XX:MaxDirectMemorySize设定本机直接内存可用大小,如果不指定,则默认与java堆内存大小相同。JDK中可以通过反射获取Unsafe类(Unsafe的getUnsafe()方法只有启动类加载器Bootstrap才能返回实例)直接操作本机直接内存。通过使用-XX:MaxDirectMemorySize=10M,限制最大可使用的本机直接内存大小为10MB,例子代码如下

packageMemory;

 

importjava.lang.reflect.Field;

 

public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 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直接想操作系统申请内存

            unsafe.allocateMemory(_1MB);

        }

    }

}

  • 1
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值