原文地址: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。