从内存溢出看Java 环境中的内存结构
作為有個java程序員,我想大家對下面出現(xiàn)的這幾個場景并不陌生,倍感親切,深惡痛絕,抓心撓肝,一定會回過頭來問為什么為什么為什么會這樣,嘿嘿,讓我們看一下我們日常在開發(fā)過程中接觸內存溢出的異常:
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內存區(qū)域以及jvm規(guī)范有比較深的了解。
既然拋出了異常,首先我們肯定這些都是內存異常,只是內存異常中的不同種類,我們就試著了解一下為什么會出現(xiàn)以上的異常,可以看出有兩種異常狀況::
OutOfMemoryError
StackOverflowError
其中OutOfMemoryError是在程序無法申請到足夠的內存的時候拋出的異常,StackOverflowError是線程申請的棧深度大于虛擬機所允許的深度所拋出的異常。 可是從上面列出的異常內容也可以看出在OutOfMemoryError類型的一場中也存在這很多異常的可能。這是為什么?以為是在內存的不同結構中出現(xiàn)的錯誤,所以拋出的異常也就形形色色,說道這我們不得不介紹一下java的內存結構,請看下圖(從網上摘的):
在運行時的內存區(qū)域有5個部分,Method Area(方法區(qū)),Java stack(java 虛擬機棧),Native MethodStack(本地方法棧),Heap(堆),Program Counter Regster(程序計數(shù)器)。從圖中看出方法區(qū)和堆用黃色標記,和其他三個區(qū)域的不同點就是,方法區(qū)和堆是線程共享的,所有的運行在jvm上的程序都能訪問這兩個區(qū)域,堆,方法區(qū)和虛擬機的生命周期一樣,隨著虛擬機的啟動而存在,而棧和程序計數(shù)器是依賴用戶線程的啟動和結束而建立和銷毀。
Program Counter Regster(程序計數(shù)器):每一個用戶線程對應一個程序計數(shù)器,用來指示當前線程所執(zhí)行字節(jié)碼的行號。由程序計數(shù)器給文字碼解釋器提供嚇一條要執(zhí)行的字節(jié)碼的的位置。根據jvm規(guī)范,在這個區(qū)域中不會拋出OutOfMemoryError的內存異常。
Java stack(java 虛擬機棧):這個區(qū)域是最容易出現(xiàn)內存異常的區(qū)域,每一個線程對應生成一個線程棧,線程每執(zhí)行一個方法的時候,都會創(chuàng)建一個棧幀,用來存放方法的局部變量表,操作樹棧,動態(tài)連接,方法入口,這和C#是不一樣的,在C#CLR中沒有棧幀的概念,都是在線程棧中通過壓棧和出棧的方式進行數(shù)據的保存。jvm規(guī)范對這個區(qū)域定義了兩種內存異常,OutOfMemoryError,StackOverflowError。
Native MethodStack(本地方法棧):和虛擬機棧一樣,不同的是處理的對象不一樣,虛擬機棧處理java的字節(jié)碼,而本地棧則是處理的Native方法。其他方面一致。
Heap(堆):前面說了堆是所有線程都能訪問的,隨著虛擬機的啟動而存在,這塊區(qū)域很大,因為所有的線程都在這個區(qū)域保存實例化的對象,因為每一個類型中,每個接口實現(xiàn)類需要的內存不一樣,一個方法內的多個分支需要的內存也不盡相同,我們只有在運行的時候才能知道要創(chuàng)建多少對象,需要分配多大的地址空間。GC關注的正是這樣的部分內容,所以很多時候也將堆稱為GC堆。堆中肯定不會拋出StackOverflowError類型的異常,所以只有OutOfMemoryError相關類型的異常。
Method Area(方法區(qū)):用于存放已被虛擬機加載的類信息,常量,靜態(tài)方法,即使編譯后的代碼。同樣只能拋出OutOfMemoryError相關類型的異常。
介紹完jvm內存結構中的常見區(qū)域,下面該是和我們主題呼應的時候了,在什么情況下,在那個區(qū)域,如何才能復現(xiàn)開始提到的異常信息?從第一個開始,異常信息的內容為:
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)這個異常,那么就要處理好,不能被垃圾回收器給回收了,設置一下jvm中堆的最大值(這樣才能夠更快的出現(xiàn)錯誤),設置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();}}
然后在運行的時候設置jvm參數(shù),如下:
運行一下看看結果:
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虛擬機堆中溢出。
下面看第二個關于棧的異常,內容如下:
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)
因為是與棧相關的話,那么我們在重現(xiàn)異常的時候就要相應的將棧內存容量設置的小一些,設置棧大小的方法是設置-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();} }
運行時jvm參數(shù)的設置如下:
運行結果如下:
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) 省略下面的異常信息
第三個異常是關于perm的異常內容,我們需要的是設置方法區(qū)的大小,實現(xiàn)方式是通過設置-XX:PermSize和-XX:MaxPermSize參數(shù),內容如下:
java.lang.OutOfMemoryError: PermGen space
如果程序加載的類過多,例如tomcatweb容器,就會出現(xiàn)PermGen space異常,如果我將HeapOOM類的運行時的XX:PermSize設置為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內存相關的,內容如下:
Exception in thread "main" java.lang.OutOfMemoryErrorat sun.misc.Unsafe.allocateMemory(Native Method)at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)
DirectMemoruSize可以通過設置 -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);}} }
運行時設置的jvm參數(shù)如下:
很容易就復線了異常信息:
Exception in thread "main" java.lang.OutOfMemoryErrorat sun.misc.Unsafe.allocateMemory(Native Method)at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)
?
總結
以上是生活随笔為你收集整理的从内存溢出看Java 环境中的内存结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 到底要不要充年费,要不要充年费啊
- 下一篇: ubuntu配置jdk环境