【转】StackOverflow和OutOfMemory

原文地址:https://blog.csdn.net/weixin_40667145/article/details/78556182

 

  • 1、stackoverflow:

    每当java程序启动一个新的线程时,java虚拟机会为他分配一个栈,java栈以帧为单位保持线程运行状态;当线程调用一个方法是,jvm压入一个新的栈帧到这个线程的栈中,只要这个方法还没返回,这个栈帧就存在。 
    如果方法的嵌套调用层次太多(如递归调用),随着java栈中的帧的增多,最终导致这个线程的栈中的所有栈帧的大小的总和大于-Xss设置的值,而产生生StackOverflowError溢出异常。

  • 2、outofmemory:

  • 2.1、栈内存溢出

    java程序启动一个新线程时,没有足够的空间为改线程分配java栈,一个线程java栈的大小由-Xss设置决定;JVM则抛出OutOfMemoryError异常。

  • 2.2、堆内存溢出

    java堆用于存放对象的实例,当需要为对象的实例分配内存时,而堆的占用已经达到了设置的最大值(通过-Xmx)设置最大值,则抛出OutOfMemoryError异常。

  • 2.3、方法区内存溢出

    方法区用于存放java类的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。在类加载器加载class文件到内存中的时候,JVM会提取其中的类信息,并将这些类信息放到方法区中。 
    当需要存储这些类信息,而方法区的内存占用又已经达到最大值(通过-XX:MaxPermSize);将会抛出OutOfMemoryError异常对于这种情况的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出。这里需要借助CGLib直接操作字节码运行时,生成了大量的动态类。

  •  

    3.背景知识

    1).JVM体系结构

    2).JVM运行时数据区

    JVM内存结构的相关可以参考:

    http://my.oschina.net/sunchp/blog/369707

     

    2.堆溢出(OutOfMemoryError:java heap space)

    堆(Heap)是Java存放对象实例的地方。

    堆溢出可以分为以下两种情况,这两种情况都会抛出OutOfMemoryError:java heap space异常:

    1)内存泄漏

    内存泄漏是指对象实例在新建和使用完毕后,仍然被引用,没能被垃圾回收释放,一直积累,直到没有剩余内存可用。

    如果内存泄露,我们要找出泄露的对象是怎么被GC ROOT引用起来,然后通过引用链来具体分析泄露的原因。

    分析内存泄漏的工具有:Jprofiler,visualvm等。

    示例:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    package com.demo3;

     

    import java.util.ArrayList;

    import java.util.List;

    import java.util.UUID;

     

    public class OOMTest {

     

        public static void main(String[] args) {

            List<UUID> list = new ArrayList<UUID>();

            while (true) {

                list.add(UUID.randomUUID());

            }

        }

     

    }

    通过如下命令运行程序:

    1

    java -Xms10M -Xmx10M -XX:-UseGCOverheadLimit OOMTest

    输出结果:

    1

    2

    3

    4

    5

    6

    7

    8

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

        at sun.security.provider.DigestBase.engineDigest(DigestBase.java:163)

        at java.security.MessageDigest$Delegate.engineDigest(MessageDigest.java:576)

        at java.security.MessageDigest.digest(MessageDigest.java:353)

        at sun.security.provider.SecureRandom.engineNextBytes(SecureRandom.java:226)

        at java.security.SecureRandom.nextBytes(SecureRandom.java:455)

        at java.util.UUID.randomUUID(UUID.java:145)

        at com.demo3.Test.main(Test.java:12)

    2)内存溢出

    内存溢出是指当我们新建一个实力对象时,实例对象所需占用的内存空间大于堆的可用空间。

    如果出现了内存溢出问题,这往往是程序本生需要的内存大于了我们给虚拟机配置的内存,这种情况下,我们可以采用调大-Xmx来解决这种问题。

    示例:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    package com.demo3;

     

    import java.util.ArrayList;

    import java.util.List;

     

    public class OOMTest {

     

        public static void main(String[] args) {

            List<byte[]> buffer = new ArrayList<byte[]>();

            buffer.add(new byte[10 1024 1024]);

        }

    }

    通过如下命令运行程序:

    1

    java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest

    输出结果:

    1

    2

    3

    4

    5

    6

    7

    [GC 836K->568K(19456K), 0.0234380 secs]

    [GC 568K->536K(19456K), 0.0009309 secs]

    [Full GC 536K->463K(19456K), 0.0085383 secs]

    [GC 463K->463K(19456K), 0.0003160 secs]

    [Full GC 463K->452K(19456K), 0.0062013 secs]

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

        at com.demo3.OOMTest.main(OOMTest.java:10)

     

    4.持久带溢出(OutOfMemoryError: PermGen space)

    持久带(PermGen space)是JVM实现方法区的地方,因此该异常主要设计到方法区和方法区中的常量池。

    1).方法区

    方法区(Method Area)不仅包含常量池,而且还保存了所有已加载类的元信息。当加载的类过多,方法区放不下所有已加载的元信息时,就会抛出OutOfMemoryError: PermGen space异常。主要有以下场景:

     

    • 使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的class没有被卸载掉。

    • 如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。

    2).常量池

    常量池(Runtime Constrant Pool)专门放置源代码中的符号信息。常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值外,还包含一些以文本形式出现的符号引用,比如:类和接口的全限定名;字段的名称和描述符;方法的名称和描述符等。

    当常量池需要的空间大于常量池的实际空间时,也会抛出OutOfMemoryError: PermGen space异常。

    例如,Java中字符串常量是放在常量池中的,String.intern()这个方法运行的时候,会检查常量池中是否存和本字符串相等的对象,如果存在直接返回对常量池中对象的引用,不存在的话,先把此字符串加入常量池,然后再返回字符串的引用。那么可以通过String.intern方法来模拟一下运行时常量区的溢出.

     

    4.线程栈

    栈(JVM Stack)存放主要是栈帧( 局部变量表, 操作数栈 , 动态链接 , 方法出口信息 )的地方。注意区分栈和栈帧:栈里包含栈帧。

    与线程栈相关的内存异常有两个:

    • StackOverflowError(方法调用层次太深,内存不够新建栈帧)

    • OutOfMemoryError(线程太多,内存不够新建线程)

    1).java.lang.StackOverflowError

    栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法运行的时候,请求新建栈帧时,栈所剩空间小于战帧所需空间。

    例如,通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常 :

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    package com.demo3;

     

    public class OOMTest {

        public void stackOverFlowMethod() {

            stackOverFlowMethod();

        }

     

        public static void main(String... args) {

            OOMTest oom = new OOMTest();

            oom.stackOverFlowMethod();

        }

    }

    运行结果:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    Exception in thread "main" java.lang.StackOverflowError

        at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)

        at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)

        at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)

        at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)

        at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)

        at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)

        at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)

        at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)

        .....

    2).java.lang.OutOfMemoryError:unable to create new native thread 

    因为虚拟机会提供一些参数来保证堆以及方法区的分配,剩下的内存基本都由栈来占有,而且每个线程都有自己独立的栈空间(堆,方法区为线程共有)。所以:

    • 如果你把虚拟机参数Xss调大了,每个线程的占用的栈空间也就变大了,那么可以建立的线程数量必然减少

    • 公式:线程栈总可用内存=JVM总内存-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占用的内存

    如果-Xmx或者-XX:MaxPermSize太大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越小。

     

    上述两种情况都会导致:当创建的线程数太多时,栈内存不够用来创建新的线程,那么就会抛出java.lang.OutOfMemoryError:unable to create new native thread 异常。

    PS:由于在window平台的虚拟机中,java的线程是隐射到操作系统的内核线程上的,所以运行一下产生该异常的代码时,可能会导致操作系统假死。

     

  • 5.联系和区别

    如果在一个线程计算过程中不允许有更大的本地方法栈,那么JVM就抛出StackOverflowError

    如果本地方法栈可以动态地扩展,并且本地方法栈尝试过扩展了,但是没有足够的内容分配给它,再或者没有足够的内存为线程建立初始化本地方法栈,那么JVM抛出的就是OutOfMemoryError。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值