Java 內存溢出分析

原文地址:Java 內存溢出分析html

博客地址:http://www.moonxy.comjava

1、前言ide

Java 的 JVM 的內存通常可分爲 3 個區:堆(heap)、棧(stack)和方法區(method)。wordpress

1.1 堆區工具

1)存儲的所有是對象,每一個對象都包含一個與之對應的 Class 的信息,Class 的目的是獲得操做指令;spa

2)JVM 只有一個堆區(heap)被全部線程共享,堆中不存放基本類型和對象引用,只存放對象自己。操作系統

1.2 棧區線程

1)每一個線程包含一個棧區,棧中只保存基礎數據類型的對象和自定義對象的引用(不是對象),對象都存放在堆區中;3d

2)每一個棧中的數據(原始類型和對象引用)都是私有的,其餘棧不能訪問;code

3)棧分爲3個部分:基本類型變量區、執行環境上下文、操做指令區(存放操做指令)。

1.3 方法區

1)又叫靜態區或永久代,跟堆同樣,被全部的線程共享。方法區包含全部的 Class 和 static 變量;

2)方法區中包含的都是在整個程序中永遠惟一的元素,如 Class,static 變量;

3)運行時常量池都分配在 Java 虛擬機的方法區之中,可是從 JDK 1.7 的 HotSpot 虛擬機開始中,已經把本來放在永久代的字符串常量池移出了。

2、內存溢出分析

如下代碼驗證,建議使用 JDK 1.6,由於從 JDK 1.7 開始,HotSpot 虛擬機改進了不少,特別是方法區(永久代)。

2.1 Java 堆溢出

Java堆是用於存儲對象實例的,所以,只要咱們不斷建立對象,而且保證對象不被垃圾回收機制清除,那麼當堆中對象的大小超過了最大堆的容量限制,就會出現堆內存溢出。

堆溢出常見的異常是:java.lang.OutOfMemoryError: Java heap space

下面這段代碼,將Java堆的大小設置爲 20MB,而且不可擴展(即將堆的最小值 -Xms 參數和最大值 -Xmx 參數設置爲相等的 20MB,來避免堆自動擴展);經過參數 -XX:+HeapDumpOnOutOfMemoryError 可讓虛擬機在出現內存溢出異常時生成當前內存堆的快照,以便過後進行分析;-verbose:gc 的做用是在虛擬機發生內存回收時在輸出設備顯示信息。

package com.java.error; import java.util.ArrayList; import java.util.List; /** * VM Args:-verbose:gc -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * Error:java.lang.OutOfMemoryError: Java heap space * @author moonxy * */
public class HeapOOM { public static void main(String[] args) { List<HeapOOM> list = new ArrayList<HeapOOM>(); while(true) { list.add(new HeapOOM()); } } }

運行後顯示以下:

[GC (Allocation Failure)  5380K->3745K(19968K), 0.0042158 secs]
[GC (Allocation Failure)  9287K->9710K(19968K), 0.0058399 secs]
[Full GC (Ergonomics)  9710K->7589K(19968K), 0.1200134 secs]
[Full GC (Ergonomics)  16387K->12869K(19968K), 0.1112792 secs]
[Full GC (Ergonomics)  16428K->16382K(19968K), 0.1711686 secs]
[Full GC (Allocation Failure)  16382K->16370K(19968K), 0.1371103 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid1204.hprof ...
Heap dump file created [28024534 bytes in 0.077 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:261)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
	at java.util.ArrayList.add(ArrayList.java:458)
	at com.java.error.OutOfMemoryJavaHeapSpaceTest.main(OutOfMemoryJavaHeapSpaceTest.java:19)

控制檯打印了OutOfMemoryError,而且提示是Java heap發生的內存溢出,同時生成了dump文件。對於Java堆溢出,通常經過內存映像分析工具(如Eclipse Memory Analyzer)對dump文件進行堆快照分析,確認內存中的對象是否是必要的:

若是對象不是必要的,那就屬於內存泄漏,須要分析爲何對象沒有被回收;

若是對象確實有必要繼續存在,那就屬於內存溢出,須要經過將堆的大小調高(-Xms-Xmx)來避免內存溢出。

此處比較一下內存泄漏和內存溢出這兩個概念,二者一般一塊兒說,可是兩個倒是不一樣的概念,彼此之間又有聯繫,以下:

內存泄漏 memory leak:是指程序在申請內存後,沒法釋放已經申請的內存空間,一次內存泄露彷佛不會有大的影響,但內存泄漏堆積後的後果就是內存溢出;

內存溢出 out of memory:是指程序申請內存後,沒有足夠的內存供申請者使用或將 long 類型的數據存儲到 int 類型的存儲空間時,就會出現 OOM 錯誤。

2.2 虛擬機棧和本地方法棧溢出

因爲HotSpot虛擬機不區分虛擬機棧和本地方法棧,所以,對於 HotSpot 來講,-Xoss參數(設置本地方法棧大小)是無效的,棧容量只由 -Xss 設置。

在Java虛擬機規範中,這個區域有兩種異常狀況:

若是線程運行時的棧幀的總大小超過虛擬機限制的大小,會拋出 StackOverflowError,這一點一般發生在遞歸運行時,棧溢出容易出現:java.lang.StackOverflowError

若是虛擬機棧設置爲能夠動態擴展,而且在擴展時沒法申請到足夠內存,則會拋出 OutOfMemoryError,此時容易出現:java.lang.OutOfMemoryError: unable to create native thread

StackOverflowError

/** * VM Args:-Xss128k * Error:java.lang.StackOverflowError * @author moonxy */
public class JavaVMStackSOF { private int stackLength = 1; public void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) throws Throwable { JavaVMStackSOF oom = new JavaVMStackSOF(); try { oom.stackLeak(); } catch (Throwable e) { System.out.println("stack length:" + oom.stackLength); throw e; } } }

運行後顯示以下:

994
Exception in thread "main" java.lang.StackOverflowError
	at com.java.error.StackOverflowTest.StackOver(StackOverflowTest.java:15)
	at com.java.error.StackOverflowTest.StackOver(StackOverflowTest.java:16)
	at com.java.error.StackOverflowTest.StackOver(StackOverflowTest.java:16)
	at com.java.error.StackOverflowTest.StackOver(StackOverflowTest.java:16)
	at com.java.error.StackOverflowTest.StackOver(StackOverflowTest.java:16)
     ……

OutOfMemoryError

不建議執行以下驗證,容易發生系統假死,由於在 Windows 平臺的虛擬機中,Java 線程是映射到操做系統的內核線程中的。

/** * VM Args:-verbose:gc -Xss2m * Error:java.lang.OutOfMemoryError: unable to create native thread * @author moonxy * */
public class JavaVMStackOOM { private void dontStop() { while(true) { } } public void stackLeakByThread() { while(true) { Thread thread = new Thread(new Runnable(){ @Override public void run() { dontStop(); } }); thread.start(); } } public static void main(String[] args) { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); } }

運行後顯示以下:

java.lang.OutOfMemoryError: unable to create new native thread

線程須要消耗棧容量,而操做系統分配給每一個進程的內存是有限制的。考慮以下場景:系統總內存 6G,JVM分配了 2G 內存,其中堆內存分配了 1G,方法區(永久代)分配 512M,忽略其餘較小的內存分配,則剩餘分配給虛擬機棧和本地方法棧的內存就只有512M,頗有可能沒有足夠的可用內存建立不少的線程。就有可能出現 java.lang.OutOfMemoryError: unable to create new native thread。若是是這種狀況,考慮減少堆內存大小或適當減小每一個線程的棧分配大小。

線程會佔用內存,若是每一個線程都佔用更多內存,總體上將消耗更多的內存。每一個線程默認佔用內存大小取決於 JVM 實現。能夠利用 -Xss 參數限制線程內存大小,下降總內存消耗。例如,JVM 默認每一個線程佔用 1M 內存,應用有 500個 線程,那麼將消耗 500M 內存空間。若是實際上 256K 內存足夠線程正常運行,配置 -Xss256k,那麼 500 個線程將只須要消耗 125M 內存。(注意,若是 -Xss 設置的太低,又將會產生 java.lang.StackOverflowError 錯誤)

2.3 方法區溢出

方法區存儲的是虛擬機加載的類信息、常量、靜態變量、JIT 編譯器編譯後的代碼等數據,在 JDK1.7以前,HotSpot 都是使用 "永久代" 來管理這些數據,也就是說,方法區的數據,其實也是屬於堆內存的,會佔用堆內存。

所以:方法區的數據大小 < -XX:MaxPermSize < -Xmx

咱們只要限制一下永久代的大小(-XX:MaxPermSize),很容易就會發生方法區溢出,此時方法區容易出現:java.lang.OutOfMemoryError: PermGen space。能夠經過設置 -XX:PermSize-XX:MaxPermSize 來限制方法區大小。

/** * VM Args:-verbose:gc -XX:PermSize=10M -XX:MaxPermSize=10M * Error:java.lang.OutOfMemoryError: PermGen space * @author moonxy * */
public class OutOfMemoryPermGenSpaceTest { public static void main(String[] args) { List<String> oomPgsList = new ArrayList<String>(); int i = 0; while(true) { oomPgsList.add(String.valueOf(i++).intern()); } } }

運行後結果以下:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

若是咱們在 JDK 1.7 的環境下執行上面一樣的代碼,會發現 while 循環會一直執行下去。緣由是在 JDK 1.7 中,HotSpot 已經再也不將常量放入永久代中進行管理,而是放到內存上限是本機內存的 Native Memory 中,這樣作的好處就是減小了內存溢出的概率(沒有了-XX:MaxPermSize 的限制),同時常量再也不佔用堆的內存。這種 "去永久代" 的作法,從 JDK 1.7 已經開始進行。終於在 JDK 1.8 中,被完全的執行了,永久代被完全去掉了,同時 HotSpot 新增了一塊叫作 Mataspace 的區域,並提供了 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 參數,來設置運行 Java 虛擬機使用的 Metaspace 的初始容量和最大容量。

2.4 本機直接內存溢出

直接內存(Direct Memory)並非虛擬機運行時數據區的一部分,也不是 Java 虛擬機規範中定義的內存區域。可是這部份內存也被頻繁的使用,並且也可能致使 OutOfMemoryError 出現,即:java.lang.OutOfMemory

本機直接內存的容量能夠經過 -XX:MaxDirectMemorySize 指定,若是不指定,默認與 Java 堆最大值(-Xmx指定)同樣。

本機直接內存溢出的一個明顯特徵是,dump 文件很小,由於主要對象都在 direct memory了,而且異常信息也不會說明是在哪一個區域發生內存溢出,就像這樣:java.lang.OutOfMemoryError

/** * * VM Args:-verbose:gc -XX:MaxDirectMemorySize * Error:java.lang.OutOfMemory * @author moonxy * */
public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1MB); } } }

運行結果以下:

Exception in thread "main" java.lang.OutOfMemoryError

綜上,當 Java 應用比較大時,能夠設置以下的 JVM 參數:

-Xms256m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m

注意:

方法區存儲的對象類型在 JDK 1.六、1.七、1.8 等不一樣的 JDK 版本中都有變化,在具體的開發中,須要特別注意 JDK 版本的不一樣,而致使的一些差別。

JDK 1.6 以後提供了 jvisualvm 工具,用於監控 JVM 內存的使用狀況,一般在 jdk 的 bin 目錄下,直接運行 jvisualvm.exe,能夠監控內存泄露、跟蹤垃圾回收、運行時內存、cpu 分析、線程分析等。

相關文章
相關標籤/搜索