JVM-03内存区域与内存溢出异常(下)【OutOfMemoryError案例】
文章目錄
- 思維導(dǎo)圖
- Java堆溢出
- 前置操作
- 測(cè)試類
- 結(jié)果
- 使用mat分析
- 內(nèi)存泄露Memory Leak
- 內(nèi)存溢出Memory Overflow
- 虛擬機(jī)棧和本地方法棧溢出
- 概述
- StackOverflowError
- OutofMemoryError
- 方法區(qū)和運(yùn)行時(shí)常量池溢出
- 測(cè)試
- 本機(jī)直接內(nèi)存溢出
思維導(dǎo)圖
接下來(lái),我們來(lái)通過(guò)示例來(lái)演示下出現(xiàn)異常的場(chǎng)景。
Java堆溢出
前置操作
JVM參數(shù)官網(wǎng) :http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
為了更加方便的制造出內(nèi)存溢出的錯(cuò)誤,我們需要通過(guò)JVM提供的參數(shù)來(lái)設(shè)置虛擬機(jī)啟動(dòng)參數(shù),因?yàn)槲覀兪鞘褂玫腎DE,設(shè)置如下
-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError- -Xms 初始堆大小。-Xmx 最大堆大小。 設(shè)置成一樣即是不可擴(kuò)展的意思
- -XX:+HeapDumpOnOutOfMemoryError 讓虛擬機(jī)在發(fā)生內(nèi)存溢出時(shí) Dump 出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲(chǔ)快照,以便分析用
如果Java Application 下沒(méi)有對(duì)應(yīng)的類,選中要測(cè)試的類,右鍵Run Configuratons ,然后在Java Application處,右鍵New即可。
測(cè)試類
package com.artisan.memory;import java.util.ArrayList; import java.util.List;public class HeapOOM {static class OOMObject {}// 如果堆中沒(méi)有內(nèi)存完成實(shí)例分配,并且對(duì)也無(wú)法再擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();while (true) {list.add(new OOMObject());}} }結(jié)果
運(yùn)行日志輸出如下:
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid16572.hprof ... Heap dump file created [13008837 bytes in 0.079 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:2245)at java.util.Arrays.copyOf(Arrays.java:2219)at java.util.ArrayList.grow(ArrayList.java:242)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)at java.util.ArrayList.add(ArrayList.java:440)at com.artisan.memory.HeapOOM.main(HeapOOM.java:16)當(dāng)java應(yīng)用程序出現(xiàn)堆內(nèi)存溢出的時(shí)候,異常堆棧信息為java.lang.OutOfMemoryError 后面會(huì)跟著 Java heap space
使用mat分析
要解決這個(gè)區(qū)域的異常,一般的手段是先通過(guò)內(nèi)存映射分析工具比如Eclipse Memory Analyzer 對(duì)dump出來(lái)的堆轉(zhuǎn)儲(chǔ)快照進(jìn)行分析,重點(diǎn)是確認(rèn)內(nèi)存中的對(duì)象是否是必要的,就是要分先分清到底是出現(xiàn)了內(nèi)存泄露(Memory Leak) 還是 內(nèi)存溢出(Memory Overflow).
我們使用mat來(lái)分析下剛才產(chǎn)生的dump文件
Shallow Size :對(duì)象自身占用的內(nèi)存大小,不包括它引用的對(duì)象。
針對(duì)非數(shù)組類型的對(duì)象,它的大小就是對(duì)象與它所有的成員變量大小的總和。當(dāng)然這里面還會(huì)包括一些java語(yǔ)言特性的數(shù)據(jù)存儲(chǔ)單元。
針對(duì)數(shù)組類型的對(duì)象,它的大小是數(shù)組元素對(duì)象的大小總和。
Retained Size=當(dāng)前對(duì)象大小+當(dāng)前對(duì)象可直接或間接引用到的對(duì)象的大小總和。(間接引用的含義:A->B->C, C就是間接引用)
換句話說(shuō),Retained Size就是當(dāng)前對(duì)象被GC后,從Heap上總共能釋放掉的內(nèi)存。
不過(guò),釋放的時(shí)候還要排除被GC Roots直接或間接引用的對(duì)象。他們暫時(shí)不會(huì)被被當(dāng)做Garbage
內(nèi)存泄露Memory Leak
如果是內(nèi)存泄露,可進(jìn)一步通過(guò)工具排查看看泄露對(duì)象到GC Roots的引用鏈。于是就能找到泄露對(duì)象是通過(guò)怎樣的路徑與GC Roots相關(guān)聯(lián)并導(dǎo)致垃圾收集器無(wú)法自動(dòng)回收他們的,從而比較準(zhǔn)確的定位到泄漏代碼的位置
內(nèi)存溢出Memory Overflow
如果不存在泄露,換句話說(shuō)就是內(nèi)存中的對(duì)象確實(shí)都還必須存活著,那就應(yīng)該檢查虛擬機(jī)的堆參數(shù)(-Xms 和 -Xms),與物理機(jī)器內(nèi)存對(duì)比存看下是否可以調(diào)大,從代碼是否存在某些生命周期過(guò)長(zhǎng),持有狀態(tài)時(shí)間工廠的情況,嘗試減少程序運(yùn)行期的內(nèi)存消耗。
虛擬機(jī)棧和本地方法棧溢出
概述
由于在Hotspot虛擬機(jī)中并不區(qū)分虛擬機(jī)棧和本地方法棧,因此對(duì)于Hotspot來(lái)說(shuō),雖然-Xoss參數(shù)(設(shè)置本地方法棧大小)存在,但是無(wú)效的。 棧容量只由-Xss參數(shù)設(shè)定。
關(guān)于虛擬機(jī)棧和本地方法棧,在Java虛擬機(jī)規(guī)范中描述了兩種異常
- 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverflowError異常
- 如果虛擬機(jī)在擴(kuò)展棧時(shí)無(wú)法申請(qǐng)到最夠的內(nèi)存空間,則拋出OutOfMemoryError異常
雖然把異常分成兩種情況,看起來(lái)很嚴(yán)謹(jǐn),其實(shí)卻存在一些重疊的地方: 當(dāng)棧空間無(wú)法繼續(xù)分配時(shí),是內(nèi)存太小 還是已經(jīng)使用的棧空間過(guò)大,本質(zhì)上只是對(duì)同一件事情的兩種描述而已。
StackOverflowError
單線程, 通過(guò)調(diào)整-Xss參數(shù)減少棧內(nèi)存容量 ----> StackOverflowError
單線程,定義了大量的本地變量,增大此方法幀中本地變量表的長(zhǎng)度----> StackOverflowError
輸出日志:
Stack length:987Exception in thread "main" java.lang.StackOverflowErrorat com.artisan.memory.JVMStatckSOF.statckLeak(JVMStatckSOF.java:25)at com.artisan.memory.JVMStatckSOF.statckLeak(JVMStatckSOF.java:26)at com.artisan.memory.JVMStatckSOF.statckLeak(JVMStatckSOF.java:26)at com.artisan.memory.JVMStatckSOF.statckLeak(JVMStatckSOF.java:26)at com.artisan.memory.JVMStatckSOF.statckLeak(JVMStatckSOF.java:26)試驗(yàn)結(jié)果表明,在單線程先,無(wú)論是由于幀棧太大還是由于虛擬機(jī)棧容量太小,當(dāng)內(nèi)存無(wú)法分配的時(shí)候,虛擬機(jī)拋出的都是StackOverflowError異常。
OutofMemoryError
如果不限制單線程,通過(guò)不斷的建立線程的方式倒是可以產(chǎn)生內(nèi)存溢出
的場(chǎng)景(謹(jǐn)慎使用,如果是windows會(huì)使電腦卡死)
如上述代碼產(chǎn)生的內(nèi)存溢出異常和棧空間是否足夠大不存在任何聯(lián)系,或者準(zhǔn)確的說(shuō),在這種情況下,為每個(gè)線程的棧分配的內(nèi)存越大,反而越容易產(chǎn)生內(nèi)存溢出。
操作系統(tǒng)分配給每個(gè)進(jìn)程的內(nèi)存是有限制的,虛擬機(jī)提供了參數(shù)來(lái)控制Java堆和方法區(qū)的這兩部分內(nèi)存的最大值。 剩余內(nèi)存為操作系統(tǒng)總內(nèi)存-Xmx(最大堆容量)-MaxPermSize(最大方法區(qū)容量)-程序計(jì)數(shù)器消耗的內(nèi)存(很小可忽略不計(jì))如果虛擬機(jī)進(jìn)程本身消耗的內(nèi)存不計(jì)算在內(nèi),剩下的內(nèi)存就由虛擬機(jī)棧和本地方法棧瓜分了。 每個(gè)線程分配到的棧容量越大(-Xss設(shè)置),可以建立的線程數(shù)自然越少,建立線程的時(shí)候就越容易把剩下的內(nèi)存耗盡。
異常信息
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread方法區(qū)和運(yùn)行時(shí)常量池溢出
由于運(yùn)行時(shí)常量池是方法區(qū)(永久代)的一部分,因此將這兩個(gè)區(qū)域的移除測(cè)試放到一起。
我們知道JDK1.7開(kāi)始逐步“去永久代”,所以我們這個(gè)案例是在JDK1.6中的版本測(cè)試的。
String.intern()是一個(gè)Native方法,它的作用是:如果字符串常量池中包含一個(gè)等于此String對(duì)象的字符串,則返回代表池中這個(gè)字符串的String對(duì)象,否則將此String對(duì)象包含的字符串添加到常量池中,并且返回此String對(duì)象的引用。
測(cè)試
package com.artisan.memory;import java.util.ArrayList; import java.util.List;/*** * * @ClassName: RuntimeConstantPoolOOM* * @Description: JDK1.6中運(yùn)行* * VM Args -XX:PermSize=10M -XX:MaxPermSize=10M* * @author: Mr.Yang* * @date: 2018年7月27日 下午9:57:43*/ public class RuntimeConstantPoolOOM {public static void main(String[] args) {// 使用List保持常量池引用,避免Full GC回收常量池行為List<String> list = new ArrayList<String>();// 10M的PermSize在integer范圍內(nèi)最夠產(chǎn)生OOM了int i = 1 ;while (true) {list.add(String.valueOf(i++).intern());}} }輸出
Exception in thread "main" java.lang.OutOfMemoryError: PermGen spaceat java.lang.String.intern(Native Method)at com.artisan.memory.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:27)如果在JDK1.7中會(huì)得到不同的結(jié)果,該程序會(huì)一直運(yùn)行下去。
本機(jī)直接內(nèi)存溢出
DirectMemory容量可通過(guò) -XX:MaxDirectorySize指定,如果不指定,這默認(rèn)和Java堆最大內(nèi)存(-Xmx指定)一樣。
由于DirectMemory導(dǎo)致的內(nèi)存溢出,一個(gè)明顯的特征是在Heap Dump文件中不會(huì)看見(jiàn)明顯的異常,如果發(fā)現(xiàn)OOM之后Dump很小,而程序中又直接或者間接的使用了NIO,那就可以考慮下是不是這個(gè)方面的原因。
總結(jié)
以上是生活随笔為你收集整理的JVM-03内存区域与内存溢出异常(下)【OutOfMemoryError案例】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JVM-02内存区域与内存溢出异常(中)
- 下一篇: JVM-04垃圾收集Garbage Co