从内存溢出看Java 环境中的内存结构
作為有個java程序員,我想大家對下面出現(xiàn)的這幾個場景并不陌生,倍感親切,深惡痛絕,抓心撓肝,一定會回過頭來問為什么為什么為什么會這樣,嘿嘿,讓我們看一下我們?nèi)粘T陂_發(fā)過程中接觸內(nèi)存溢出的異常:
Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Unknown Source)at java.util.Arrays.copyOf(Unknown Source)at java.util.ArrayList.grow(Unknown Source)at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)at java.util.ArrayList.ensureCapacityInternal(Unknown Source)at java.util.ArrayList.add(Unknown Source)at oom.HeapOOM.main(HeapOOM.java:21)
Exception in thread "main" java.lang.StackOverflowErrorat java.nio.CharBuffer.arrayOffset(Unknown Source)at sun.nio.cs.UTF_8.updatePositions(Unknown Source)at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)at java.nio.charset.CharsetEncoder.encode(Unknown Source)at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)at sun.nio.cs.StreamEncoder.write(Unknown Source)at java.io.OutputStreamWriter.write(Unknown Source)at java.io.BufferedWriter.flushBuffer(Unknown Source)at java.io.PrintStream.write(Unknown Source)at java.io.PrintStream.print(Unknown Source)at java.io.PrintStream.println(Unknown Source)
java.lang.OutOfMemoryError: PermGen space
Exception in thread "main" java.lang.OutOfMemoryErrorat sun.misc.Unsafe.allocateMemory(Native Method)at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)
是不是有大家很熟悉的,遇見這樣的問題解決起來可能不簡單,但是如果現(xiàn)在讓大家寫個程序,故意讓程序出現(xiàn)下面的異常,估計能很快寫出來的也不是很多,這就要求開發(fā)人員對于java內(nèi)存區(qū)域以及jvm規(guī)范有比較深的了解。
既然拋出了異常,首先我們肯定這些都是內(nèi)存異常,只是內(nèi)存異常中的不同種類,我們就試著了解一下為什么會出現(xiàn)以上的異常,可以看出有兩種異常狀況::
OutOfMemoryError
StackOverflowError
其中OutOfMemoryError是在程序無法申請到足夠的內(nèi)存的時候拋出的異常,StackOverflowError是線程申請的棧深度大于虛擬機(jī)所允許的深度所拋出的異常。 可是從上面列出的異常內(nèi)容也可以看出在OutOfMemoryError類型的一場中也存在這很多異常的可能。這是為什么?以為是在內(nèi)存的不同結(jié)構(gòu)中出現(xiàn)的錯誤,所以拋出的異常也就形形色色,說道這我們不得不介紹一下java的內(nèi)存結(jié)構(gòu),請看下圖(從網(wǎng)上摘的):
在運(yùn)行時的內(nèi)存區(qū)域有5個部分,Method Area(方法區(qū)),Java stack(java 虛擬機(jī)棧),Native MethodStack(本地方法棧),Heap(堆),Program Counter Regster(程序計數(shù)器)。從圖中看出方法區(qū)和堆用黃色標(biāo)記,和其他三個區(qū)域的不同點(diǎn)就是,方法區(qū)和堆是線程共享的,所有的運(yùn)行在jvm上的程序都能訪問這兩個區(qū)域,堆,方法區(qū)和虛擬機(jī)的生命周期一樣,隨著虛擬機(jī)的啟動而存在,而棧和程序計數(shù)器是依賴用戶線程的啟動和結(jié)束而建立和銷毀。
Program Counter Regster(程序計數(shù)器):每一個用戶線程對應(yīng)一個程序計數(shù)器,用來指示當(dāng)前線程所執(zhí)行字節(jié)碼的行號。由程序計數(shù)器給文字碼解釋器提供嚇一條要執(zhí)行的字節(jié)碼的的位置。根據(jù)jvm規(guī)范,在這個區(qū)域中不會拋出OutOfMemoryError的內(nèi)存異常。
Java stack(java 虛擬機(jī)棧):這個區(qū)域是最容易出現(xiàn)內(nèi)存異常的區(qū)域,每一個線程對應(yīng)生成一個線程棧,線程每執(zhí)行一個方法的時候,都會創(chuàng)建一個棧幀,用來存放方法的局部變量表,操作樹棧,動態(tài)連接,方法入口,這和C#是不一樣的,在C#CLR中沒有棧幀的概念,都是在線程棧中通過壓棧和出棧的方式進(jìn)行數(shù)據(jù)的保存。jvm規(guī)范對這個區(qū)域定義了兩種內(nèi)存異常,OutOfMemoryError,StackOverflowError。
Native MethodStack(本地方法棧):和虛擬機(jī)棧一樣,不同的是處理的對象不一樣,虛擬機(jī)棧處理java的字節(jié)碼,而本地棧則是處理的Native方法。其他方面一致。
Heap(堆):前面說了堆是所有線程都能訪問的,隨著虛擬機(jī)的啟動而存在,這塊區(qū)域很大,因為所有的線程都在這個區(qū)域保存實例化的對象,因為每一個類型中,每個接口實現(xiàn)類需要的內(nèi)存不一樣,一個方法內(nèi)的多個分支需要的內(nèi)存也不盡相同,我們只有在運(yùn)行的時候才能知道要創(chuàng)建多少對象,需要分配多大的地址空間。GC關(guān)注的正是這樣的部分內(nèi)容,所以很多時候也將堆稱為GC堆。堆中肯定不會拋出StackOverflowError類型的異常,所以只有OutOfMemoryError相關(guān)類型的異常。
Method Area(方法區(qū)):用于存放已被虛擬機(jī)加載的類信息,常量,靜態(tài)方法,即使編譯后的代碼。同樣只能拋出OutOfMemoryError相關(guān)類型的異常。
介紹完jvm內(nèi)存結(jié)構(gòu)中的常見區(qū)域,下面該是和我們主題呼應(yīng)的時候了,在什么情況下,在那個區(qū)域,如何才能復(fù)現(xiàn)開始提到的異常信息?從第一個開始,異常信息的內(nèi)容為:
Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Unknown Source)at java.util.Arrays.copyOf(Unknown Source)at java.util.ArrayList.grow(Unknown Source)at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)at java.util.ArrayList.ensureCapacityInternal(Unknown Source)at java.util.ArrayList.add(Unknown Source)at oom.HeapOOM.main(HeapOOM.java:21)
可想而知是在堆中出現(xiàn)的問題,如何重現(xiàn),由于是在堆中出現(xiàn)這個異常,那么就要處理好,不能被垃圾回收器給回收了,設(shè)置一下jvm中堆的最大值(這樣才能夠更快的出現(xiàn)錯誤),設(shè)置jvm值的方法是通過-Xms(堆的最小值),-Xmx(堆的最大值)。下面動手試一下:
package oom;import java.util.ArrayList; import java.util.List;import testbean.UserBean;/*** * * @author Think* */ public class HeapOOM {static class OOMObject {}public static void main(String[] args) {List<UserBean> users = new ArrayList<UserBean>();while (true) {users.add(new UserBean());}} }
UserBean對象定義如下:
package testbean;public class UserBean {String name;int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public UserBean() {super();}}
然后在運(yùn)行的時候設(shè)置jvm參數(shù),如下:
運(yùn)行一下看看結(jié)果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Unknown Source)at java.util.Arrays.copyOf(Unknown Source)at java.util.ArrayList.grow(Unknown Source)at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)at java.util.ArrayList.ensureCapacityInternal(Unknown Source)at java.util.ArrayList.add(Unknown Source)at oom.HeapOOM.main(HeapOOM.java:21)
成功在java虛擬機(jī)堆中溢出。
下面看第二個關(guān)于棧的異常,內(nèi)容如下:
Exception in thread "main" java.lang.StackOverflowErrorat java.nio.CharBuffer.arrayOffset(Unknown Source)at sun.nio.cs.UTF_8.updatePositions(Unknown Source)at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)at java.nio.charset.CharsetEncoder.encode(Unknown Source)at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)at sun.nio.cs.StreamEncoder.write(Unknown Source)at java.io.OutputStreamWriter.write(Unknown Source)at java.io.BufferedWriter.flushBuffer(Unknown Source)at java.io.PrintStream.write(Unknown Source)at java.io.PrintStream.print(Unknown Source)at java.io.PrintStream.println(Unknown Source)
因為是與棧相關(guān)的話,那么我們在重現(xiàn)異常的時候就要相應(yīng)的將棧內(nèi)存容量設(shè)置的小一些,設(shè)置棧大小的方法是設(shè)置-Xss參數(shù),看如下實現(xiàn):
package oom;import testbean.Recursion;/*** * * @author Think* */ public class VMStackOOM { public static void main(String[] args) {Recursion recursion = new Recursion();try {recursion.recursionself();} catch (Throwable e) {System.out.println("current value :" + recursion.currentValue);throw e;}}}
Recursion的定義如下:
package testbean;public class Recursion {public int currentValue = 0;public void recursionself() {currentValue += 1;recursionself();} }
運(yùn)行時jvm參數(shù)的設(shè)置如下:
運(yùn)行結(jié)果如下:
current value :999 Exception in thread "main" java.lang.StackOverflowErrorat testbean.Recursion.recursionself(Recursion.java:7)at testbean.Recursion.recursionself(Recursion.java:8)at testbean.Recursion.recursionself(Recursion.java:8)at testbean.Recursion.recursionself(Recursion.java:8)at testbean.Recursion.recursionself(Recursion.java:8)at testbean.Recursion.recursionself(Recursion.java:8) 省略下面的異常信息
第三個異常是關(guān)于perm的異常內(nèi)容,我們需要的是設(shè)置方法區(qū)的大小,實現(xiàn)方式是通過設(shè)置-XX:PermSize和-XX:MaxPermSize參數(shù),內(nèi)容如下:
java.lang.OutOfMemoryError: PermGen space
如果程序加載的類過多,例如tomcatweb容器,就會出現(xiàn)PermGen space異常,如果我將HeapOOM類的運(yùn)行時的XX:PermSize設(shè)置為2M,如下:
那么程序就不會執(zhí)行成功,執(zhí)行的時候出現(xiàn)如下異常:
Error occurred during initialization of VM java.lang.OutOfMemoryError: PermGen spaceat sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Unknown Source)at sun.misc.Launcher.<init>(Unknown Source)at sun.misc.Launcher.<clinit>(Unknown Source)at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
第四個異常估計遇到的人就不多了,是DirectMemory內(nèi)存相關(guān)的,內(nèi)容如下:
Exception in thread "main" java.lang.OutOfMemoryErrorat sun.misc.Unsafe.allocateMemory(Native Method)at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)
DirectMemoruSize可以通過設(shè)置 -XX:MaxDirectMemorySize參數(shù)指定容量大小,如果不指定的話,那么就跟堆的最大值一致,下面是代碼實現(xiàn):
package oom;import java.lang.reflect.Field;import sun.misc.Unsafe;/*** * * @author Think* */ public class DirectMemoryOOM {private static final int _1MB = 1024 * 1024;public static void main(String[] args) throws IllegalArgumentException,IllegalAccessException {Field unsafeField = Unsafe.class.getDeclaredFields()[0];unsafeField.setAccessible(true);Unsafe unsafe = (Unsafe) unsafeField.get(null);while (true) {unsafe.allocateMemory(_1MB);}} }
運(yùn)行時設(shè)置的jvm參數(shù)如下:
很容易就復(fù)線了異常信息:
Exception in thread "main" java.lang.OutOfMemoryErrorat sun.misc.Unsafe.allocateMemory(Native Method)at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)
?
總結(jié)
以上是生活随笔為你收集整理的从内存溢出看Java 环境中的内存结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 到底要不要充年费,要不要充年费啊
- 下一篇: 飞向别人的床是谁唱的啊?