资讯专栏INFORMATION COLUMN

Java内存区域及内存溢出

cheukyin / 2903人阅读

摘要:直接通过可以造成本机内存溢出。小节内存区域描述异常程序计数器略略略虚拟机栈存放编译器可知的各种基本类型,对象引用和类型每个线程的栈大小堆存放对象实例最大值最小值运行时常亮池存放编译期生成的字面量和符号引用,运行期也能放入常量池。

堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径避免垃圾回收,当到达最大堆的容量限制后就会产生Java.lang.OutOfMemoryError.

/**
 * VM Options:
 * -Xms20M
 * -Xmx20M
 * -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM{
    static class OOMObject{}
    
    public static void main(String[] args){
        List list = new ArrayList();
        while(true){
            list.add(new OOMObject());
        }
    }
}

结果:
GC多次执行后触发OutOfMemoryError.

栈溢出

关于虚拟机栈,在Java规范中描述了两种异常:

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

然而,在单线程下,虚拟机在栈空间不足时会尝试扩展栈空间,因此,当无法继续分配时,到底是内存太小,还是已使用的栈空间太大,其实是一回事。在实验中,单线程环境下,只会抛出StackOverflowError异常。

/**
 * VM Option:
 * -Xss160K
 */
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;
        }
    }
}

结果:

操作系统分配给每个进程的内存是有限制的,通常为操作系统限制总内存-最大堆容量(Xmx)-最大方法区容量(MaxPermSize)-程序计数器消耗。每个线程分配到的栈容量越大,可以建立的线程数目越小。

/**
 * VM Options:
 * -Xss2M
 */
public class JavaVMStackOOM{
    private void dontStop(){
        while(true){}
    }
    
    public void stackLeakByThread(){
        while(true){
            Thread thread = new Thread(new Runnable(){
                @Override
                public void run(){
                    dontStop();
                }
            });
            thread.start();
        }
    }
    
    public static void main(String[] args) throws Throwable{
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

结果:
Exception in thread "main" java.lang.outOfMemoryError: unable to create new native thread

运行时常量池溢出

运行时常量池在JDK 1.6及之前版本中在方法区中,在1.7及之后转移至堆空间。在JDK 1.6及之前版本中可以通过限制方法区大小,从而间接限制运行时常量池大小。

/**
 * ONLY WORKS BEFORE JDK 1.7
 * VM Options:
 * -XX:PermSize=10M
 * -XX:MaxPermSize=10M
public class RuntimeConstantPoolOOM{
    public static void main(String[] args){
        List list = new ArrayList();
        int i = 0;
        while(true){
            list.add(String.valueof(i++).intern());
        }
    }
}

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

方法区溢出

方法区用于存放Class相关信息,因此要使得方法区溢出,除了在JDK 1.7之前使运行时常量池溢出外,基本的思路是运行时生成大量的类去填满方法区。
结果
Exception in thread "main" java.lang.OutOfMemoryError:PermGen space

直接内存(DirectMemory)溢出

直接内存不是虚拟机运行时数据区的一部分。在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,使用Native函数库直接分配堆外内存。
DirectMemory容量可通过-XX: MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值一样(-Xmx)。直接通过allocateMemory可以造成本机内存溢出。

结果:
Exception in thread "main" java.lang.OutOfMemoryError
直接内存溢出的一个特征是Heap Dump文件中不会看先明显的异常指示。如果OOM之后Dump文件很小,而程序中又直接或间接使用了DIO,就应该检查是否直接内存溢出。

String.intern()

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

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

在JDK 1.6中,intern()方法会把首次遇到的字符串实例复制到永久代(方法区运行时常量池),返回的是这个永久代中这个字符串实例的引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用。
在JDK 1.7中,intern()实现不会再复制,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。

小节
内存区域 描述 VM Option 异常
程序计数器
虚拟机栈 存放编译器可知的各种基本类型,对象引用和returnAddress类型 -Xss160K 每个线程的栈大小 StackOverflowError/OutOfMemoryError
Java堆 存放对象实例 -Xms10M 最大值
-Xmx20M 最小值
OutOfMemory: Java heap space
运行时常亮池 存放编译期生成的字面量和符号引用,运行期也能放入常量池(string.intern())。JDK 1.7之前在方法区中,JDK 1.7及之后移至堆中 随方法区或堆设置 OutOfMemoryError
方法区 存储虚拟机加载的类信息、常亮、静态变量、即时编译器编译后的代码等数据,又称为永久代(Permanent Generation) -XX:PermSize=10M 初始值
-XX:MaxPermSize=20M 最大值
OutOfMemoryError: PermGen space
直接内存 在JDK 1.4中加入NIO类,直接分配堆外内存 -XX:MaxDirectMemorySize=10M,
如果不指定默认与-Xmx一样
OutOfMemoryError

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69402.html

相关文章

  • 【修炼内功】[JVM] 浅谈虚拟机内存模型

    摘要:也正是因此,一旦出现内存泄漏或溢出问题,如果不了解的内存管理原理,那么将会对问题的排查带来极大的困难。 本文已收录【修炼内功】跃迁之路 showImg(https://segmentfault.com/img/bVbsP9I?w=1024&h=580); 不论做技术还是做业务,对于Java开发人员来讲,理解JVM各种原理的重要性不必再多言 对于C/C++而言,可以轻易地操作任意地址的...

    sanyang 评论0 收藏0
  • 关于JVM内存溢出的原因分析解决方案探讨

    摘要:内存溢出分配的内存空间超过系统内存。内存泄漏的原因分析由大块组成堆,栈,本地方法栈,程序计数器,方法区。内存溢出的原因分析内存溢出是由于没被引用的对象垃圾过多造成没有及时回收,造成的内存溢出。小结栈内存溢出程序所要求的栈深度过大导致。 showImg(https://segmentfault.com/img/bVbweuq?w=563&h=300); 前言:JVM中除了程序计数器,其他...

    xuexiangjys 评论0 收藏0
  • JVM内存模型与运行时数据区域

    摘要:内存模型和运行时数据区域的关系主内存对应着堆,工作内存对应着栈。在的单例模式中有运用到二运行时数据区域内存区域因为的运行时数据区域一直在改善,所以不同版本之间会有不同。 一、java内存模型 showImg(https://segmentfault.com/img/remote/1460000016694250?w=1810&h=941); java定义内存模型的目的是:为了屏蔽各种...

    canopus4u 评论0 收藏0
  • JVM系列(一):深入详解JVM 内存区域总结!

    摘要:一内存区域虚拟机在运行时,会把内存空间分为若干个区域,根据虚拟机规范版的规定,虚拟机所管理的内存区域分为如下部分方法区堆内存虚拟机栈本地方法栈程序计数器。前言 在JVM的管控下,Java程序员不再需要管理内存的分配与释放,这和在C和C++的世界是完全不一样的。所以,在JVM的帮助下,Java程序员很少会关注内存泄露和内存溢出的问题。但是,一旦JVM发生这些情况的时候,如果你不清楚JVM内存的...

    Aldous 评论0 收藏0
  • 十种JVM内存溢出的情况,你碰到过几种?

    摘要:内存溢出的情况就是从类加载器加载的时候开始出现的,内存溢出分为两大类和。以下举出个内存溢出的情况,并通过实例代码的方式讲解了是如何出现内存溢出的。内存溢出问题描述元空间的溢出,系统会抛出。这样就会造成栈的内存溢出。 导言: 对于java程序员来说,在虚拟机自动内存管理机制的帮助下,不需要自己实现释放内存,不容易出现内存泄漏和内存溢出的问题,由虚拟机管理内存这一切看起来非常美好,但是一旦...

    ShevaKuilin 评论0 收藏0

发表评论

0条评论

cheukyin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<