【轉】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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章