layout: post
title: 深入JAVA虛擬機(jī)2_4OutOfMemoryError異常
categories: JVM JAVA
description: 深入JAVA虛擬機(jī)2_4OutOfMemoryError異常
keywords: JVM JAVA
注意:KOTLIN跟JAVA運(yùn)行結(jié)果可能會不同,參照樣例
http://www.itdecent.cn/p/d9f90f3ee936
2_4_1. JAVA堆溢出測試
測試思路
java堆用于存儲對象實(shí)例,只要不斷創(chuàng)建對象,并保證
GC Roots到對象之間有可達(dá)路徑來避免垃圾回收機(jī)制清
除這些對象,那么在對象數(shù)量到達(dá)最大堆的容量限制后
就會產(chǎn)生內(nèi)存溢出異常
code2_3虛擬機(jī)參數(shù)
//限制java堆的大小為20mb[-Xms為堆的最小值,-Xmx為
堆的最大值]
//XX:+HeapDumpOnOutOfMemoryError可讓虛擬機(jī)在出
現(xiàn)內(nèi)存溢出異常時(shí)Dump出當(dāng)前內(nèi)存堆轉(zhuǎn)儲快照以便事后
進(jìn)行分析
參數(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)存,導(dǎo)致內(nèi)
存不足的原因有很多,內(nèi)存泄漏只是其中的一種
解決方法
使用工具檢查GC Roots查看引用鏈上的對象是否都有存
活的意義,若確實(shí)沒有出現(xiàn)泄露的情況,應(yīng)該調(diào)整堆內(nèi)
存參數(shù)(-Xmx 和 Xms)的最大值和最小值
p.s
程序運(yùn)行參數(shù)圖
程序運(yùn)行結(jié)果圖
github鏈接(使用kotlin實(shí)現(xiàn))
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_3.kt
分 割 線
2_4_2. 虛擬機(jī)棧和本地方法棧溢出
1.虛擬機(jī)棧和本地方法棧OOM測試
測試思路
HotSpot虛擬機(jī)中不分虛擬機(jī)棧和本地方法棧,故-Xoss
參數(shù)(設(shè)置本地方法棧大小)存在但實(shí)際上是無效的,棧容
量由-Xss參數(shù)設(shè)定,關(guān)于虛擬機(jī)棧和本地方法棧java虛擬
機(jī)描述了兩種異常:
1.如果線程請求的棧深度大于虛擬機(jī)所允許的最大深度,
將拋出StackOverflowError異常
2.如果虛擬機(jī)在擴(kuò)展棧時(shí)無法申請到足夠的內(nèi)存空間,則
拋出OutOfMemoryError異常
code2_4虛擬機(jī)參數(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虛擬機(jī)棧 與 java堆 與 方法區(qū)
1.java虛擬機(jī)棧:線程私有,生命周期與線程相同,每個(gè)方
法在執(zhí)行的過程中都會創(chuàng)建一個(gè)棧幀(Stack Frame)用于
存儲局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信
息。每一個(gè)方法執(zhí)行完成的過程,就對應(yīng)一個(gè)棧幀在虛
擬機(jī)棧中入棧出棧的過程。
2.java堆:所有線程共享的一塊內(nèi)存區(qū)域,用于存放對象
實(shí)例,垃圾收集器管理的主要區(qū)域,很多時(shí)候也被稱作
"GC堆"(Garbage Collected Heap)
3.方法區(qū):所有線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬
機(jī)加載的類信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后
的代碼等數(shù)據(jù)
實(shí)驗(yàn)結(jié)果
在單線程下,無論由于棧幀太大還是虛擬機(jī)棧容量太
小,當(dāng)內(nèi)存無法分配時(shí)虛擬機(jī)拋出的都是
StackOverflowError異常
p.s
程序運(yùn)行參數(shù)圖
程序運(yùn)行結(jié)果圖
github鏈接(使用kotlin實(shí)現(xiàn))
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_4.kt
2.多線程導(dǎo)致內(nèi)存溢出異常
測試思路
如果測試時(shí)不限單線程,通過不斷創(chuàng)建線程的方式倒是
可以產(chǎn)生內(nèi)存溢出異常,這種情況下,為每個(gè)線程的棧
分配的內(nèi)存越大,反而越容易產(chǎn)生內(nèi)存溢出異常
使用工具
JProfiler用于查看線程使用情況
Idea安裝JProfiler
code2_5虛擬機(jī)參數(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)中給每個(gè)線程分配的內(nèi)存
限制為2g,虛擬機(jī)提供了參數(shù)來控制java堆和方法區(qū)這
兩部分內(nèi)存的最大值,剩余的內(nèi)存為2GB減去Xmx(最大
堆容量),再減去MaxPermSize(最大方法區(qū)容量),程序
計(jì)數(shù)器消耗內(nèi)存很小,可以忽略。
p.s
程序運(yùn)行參數(shù)圖
github鏈接(使用kotlin實(shí)現(xiàn))
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_5.kt
分 割 線
2_4_3 方法區(qū)和運(yùn)行時(shí)常量池溢出
1.運(yùn)行時(shí)常量池導(dǎo)致的內(nèi)存溢出異常
測試思路
運(yùn)行時(shí)常量池是方法區(qū)的一部分,jdk7開始逐步去除永久
代,String.intern()是一個(gè)Native方法,在jdk1.6及之前的版
本中,由于常量池分配在永久代中,我們可以通過
-XX:PermSize與 -XX:MaxPermSize限制方法區(qū)大小,從
而限制常量池容量大小
code2_6虛擬機(jī)參數(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)
}
}
實(shí)驗(yàn)結(jié)果
運(yùn)行時(shí)常量池溢出,在"OutOfMemoryError"后面跟著的提示信息
是"PermGen space",說明運(yùn)行時(shí)常量池屬于方法區(qū)(HotSpot虛擬機(jī)中的永
久代)的一部分,在jdk1.7中則不會得到相同結(jié)果,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("計(jì)算機(jī)").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()會把首次遇到的字符串實(shí)例復(fù)制在永久代中,返回的
也是永久代中這個(gè)字符串實(shí)例的引用,而StringBuilder創(chuàng)建的字符串實(shí)例
在java堆上,所以必然不是同一個(gè)引用,將返回false。而JDK1.7中的
intern()實(shí)現(xiàn)不會再復(fù)制實(shí)例,只是在常量池中記錄首次出現(xiàn)的引用,因此
intern()返回的引用和由StringBuilder創(chuàng)建的那個(gè)字符串實(shí)例是同一個(gè)。對
于str2返回false是因?yàn)?java'這個(gè)字符串在執(zhí)行StringBuilder.toString()之前
已經(jīng)出現(xiàn)過,字符串常量池已經(jīng)有它的引用,不符合首次出現(xiàn)原則,而'計(jì)
算機(jī)軟件'這個(gè)字符串則是首次出現(xiàn)返回true。





