layout: post
title: 深入JAVA虛擬機2_4OutOfMemoryError異常
categories: JVM JAVA
description: 深入JAVA虛擬機2_4OutOfMemoryError異常
keywords: JVM JAVA
注意:KOTLIN跟JAVA運行結果可能會不同,參照樣例
http://www.reibang.com/p/d9f90f3ee936
2_4_1. JAVA堆溢出測試
測試思路
java堆用于存儲對象實例,只要不斷創(chuàng)建對象邪驮,并保證
GC Roots到對象之間有可達路徑來避免垃圾回收機制清
除這些對象澜躺,那么在對象數(shù)量到達最大堆的容量限制后
就會產(chǎn)生內(nèi)存溢出異常
code2_3虛擬機參數(shù)
//限制java堆的大小為20mb[-Xms為堆的最小值,-Xmx為
堆的最大值]
//XX:+HeapDumpOnOutOfMemoryError可讓虛擬機在出
現(xiàn)內(nèi)存溢出異常時Dump出當前內(nèi)存堆轉儲快照以便事后
進行分析
參數(shù): -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
code2_3 代碼
package num2
/**
* Created by Joey_Tsai on 2018/3/5.
*/
class HeapOOM{
companion object {
class OOMObject{
}
}
}
fun main(args : Array<String>){
val list : MutableList<HeapOOM.Companion.OOMObject> = ArrayList<HeapOOM.Companion.OOMObject>();
var i = 0;
while (true){
list.add(HeapOOM.Companion.OOMObject());
println(++i);
}
}
內(nèi)存泄漏 與 內(nèi)存溢出
內(nèi)存泄漏:編寫的程序沒有正確的釋放內(nèi)存
內(nèi)存溢出:內(nèi)存不足,無法正常給程序分配內(nèi)存私恬,導致內(nèi)
存不足的原因有很多堪澎,內(nèi)存泄漏只是其中的一種
解決方法
使用工具檢查GC Roots查看引用鏈上的對象是否都有存
活的意義柜裸,若確實沒有出現(xiàn)泄露的情況缕陕,應該調(diào)整堆內(nèi)
存參數(shù)(-Xmx 和 Xms)的最大值和最小值
p.s
程序運行參數(shù)圖
程序運行結果圖
github鏈接(使用kotlin實現(xiàn))
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_3.kt
分 割 線
2_4_2. 虛擬機棧和本地方法棧溢出
1.虛擬機棧和本地方法棧OOM測試
測試思路
HotSpot虛擬機中不分虛擬機棧和本地方法棧,故-Xoss
參數(shù)(設置本地方法棧大小)存在但實際上是無效的疙挺,棧容
量由-Xss參數(shù)設定扛邑,關于虛擬機棧和本地方法棧java虛擬
機描述了兩種異常:
1.如果線程請求的棧深度大于虛擬機所允許的最大深度,
將拋出StackOverflowError異常
2.如果虛擬機在擴展棧時無法申請到足夠的內(nèi)存空間,則
拋出OutOfMemoryError異常
code2_4虛擬機參數(shù)
參數(shù) : -Xss128k
code2_4代碼
package num2
/**
* Created by Joey_Tsai on 2018/3/5.
* VM:-Xss128k
*/
public class JavaVMStackSOF{
public var stackLength : Int = 1
public fun stackLeak() : Unit{
stackLength++;
stackLeak()
}
}
fun main(args:Array<String>){
val javaVMStackSOF : JavaVMStackSOF = JavaVMStackSOF()
try {
javaVMStackSOF.stackLeak()
}catch (e : Throwable ){
println("stack length : ${javaVMStackSOF.stackLength}")
throw e
}
}
java虛擬機棧 與 java堆 與 方法區(qū)
1.java虛擬機棧:線程私有,生命周期與線程相同铐然,每個方
法在執(zhí)行的過程中都會創(chuàng)建一個棧幀(Stack Frame)用于
存儲局部變量表蔬崩,操作數(shù)棧,動態(tài)鏈接搀暑,方法出口等信
息沥阳。每一個方法執(zhí)行完成的過程,就對應一個棧幀在虛
擬機棧中入棧出棧的過程自点。
2.java堆:所有線程共享的一塊內(nèi)存區(qū)域桐罕,用于存放對象
實例,垃圾收集器管理的主要區(qū)域桂敛,很多時候也被稱作
"GC堆"(Garbage Collected Heap)
3.方法區(qū):所有線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬
機加載的類信息功炮,常量,靜態(tài)變量术唬,即時編譯器編譯后
的代碼等數(shù)據(jù)
實驗結果
在單線程下薪伏,無論由于棧幀太大還是虛擬機棧容量太
小,當內(nèi)存無法分配時虛擬機拋出的都是
StackOverflowError異常
p.s
程序運行參數(shù)圖
程序運行結果圖
github鏈接(使用kotlin實現(xiàn))
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_4.kt
2.多線程導致內(nèi)存溢出異常
測試思路
如果測試時不限單線程粗仓,通過不斷創(chuàng)建線程的方式倒是
可以產(chǎn)生內(nèi)存溢出異常嫁怀,這種情況下设捐,為每個線程的棧
分配的內(nèi)存越大,反而越容易產(chǎn)生內(nèi)存溢出異常
使用工具
JProfiler用于查看線程使用情況
Idea安裝JProfiler
code2_5虛擬機參數(shù)
參數(shù): -Xss2M
code2_5代碼
package num2
/**
* Created by Joey_Tsai on 2018/3/6.
*VM: -Xss200M
*/
class JavaVMStackOOM{
private fun dontStop() : Unit{
while (true){
}
}
public fun stackLeakByThread():Unit{
while (true){
val thread : Thread = Thread(Runnable(){
@Override
fun run(){
dontStop()
}
});
thread.start()
}
}
}
fun main(args : Array<String>){
val oom = JavaVMStackOOM()
oom.stackLeakByThread()
}
操作系統(tǒng)內(nèi)存分配
譬如塘淑,在32位的windows系統(tǒng)中給每個線程分配的內(nèi)存
限制為2g挡育,虛擬機提供了參數(shù)來控制java堆和方法區(qū)這
兩部分內(nèi)存的最大值,剩余的內(nèi)存為2GB減去Xmx(最大
堆容量)朴爬,再減去MaxPermSize(最大方法區(qū)容量),程序
計數(shù)器消耗內(nèi)存很小橡淆,可以忽略召噩。
p.s
程序運行參數(shù)圖
github鏈接(使用kotlin實現(xiàn))
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_5.kt
分 割 線
2_4_3 方法區(qū)和運行時常量池溢出
1.運行時常量池導致的內(nèi)存溢出異常
測試思路
運行時常量池是方法區(qū)的一部分,jdk7開始逐步去除永久
代,String.intern()是一個Native方法,在jdk1.6及之前的版
本中逸爵,由于常量池分配在永久代中具滴,我們可以通過
-XX:PermSize與 -XX:MaxPermSize限制方法區(qū)大小,從
而限制常量池容量大小
code2_6虛擬機參數(shù)
VM:-XX:PermSize=10M -XX:MaxPermSize=10M
code2_6代碼
package num2
/**
* Created by Joey_Tsai on 2018/3/6.
* VM:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
class RuntimeConstantPoolOOM2_6{
}
fun main(args : Array<String>){
//使用List保持著常量池引用师倔,避免Full GC回收常量池行為
val list : MutableList<String>?=ArrayList<String>();
//10MB的permSize在integer范圍內(nèi)足夠產(chǎn)生OOM了
var i : Int = 0
while (true){
list?.add(i++.toString().intern())
println(i)
}
}
實驗結果
運行時常量池溢出,在"OutOfMemoryError"后面跟著的提示信息
是"PermGen space",說明運行時常量池屬于方法區(qū)(HotSpot虛擬機中的永
久代)的一部分构韵,在jdk1.7中則不會得到相同結果,while將一直循環(huán)下去趋艘。
2.String.intern返回引用測試
code2_7代碼
package num2
/**
* Created by Joey_Tsai on 2018/3/6.
*/
public class RuntimeConstantPoolOOM2_7{
}
fun main(args: Array<String>) {
val str1 : String = StringBuilder("計算機").append("軟件").toString()
println(str1.intern() == str1)
val str2 : String = StringBuilder("ja").append("va").toString()
println(str2.intern() == str2)
}
JDK1.6中的intern() 與 JDK1.7中的intern()
在JDK1.6中,intern()會把首次遇到的字符串實例復制在永久代中疲恢,返回的
也是永久代中這個字符串實例的引用,而StringBuilder創(chuàng)建的字符串實例
在java堆上瓷胧,所以必然不是同一個引用显拳,將返回false。而JDK1.7中的
intern()實現(xiàn)不會再復制實例搓萧,只是在常量池中記錄首次出現(xiàn)的引用杂数,因此
intern()返回的引用和由StringBuilder創(chuàng)建的那個字符串實例是同一個。對
于str2返回false是因為'java'這個字符串在執(zhí)行StringBuilder.toString()之前
已經(jīng)出現(xiàn)過瘸洛,字符串常量池已經(jīng)有它的引用揍移,不符合首次出現(xiàn)原則,而'計
算機軟件'這個字符串則是首次出現(xiàn)返回true反肋。