除了程序計數器外都有可能發生OutOfMemoryError異常的可能。java
1.java堆溢出多線程
不斷建立對象,並保證GC Root到對象之間有可達路徑來避免垃圾回收機制清楚這些對象,在達到最大堆的容量限制後就會產生內存溢出異常。工具
堆大小設置爲20MB並在出現內存溢出異常時Dump出當前的內存堆轉儲快照優化
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryErrorspa
當出現java堆內存溢出時,異常堆棧信息「java.lang.OutOfMemoryError」會跟着進一步提示「java heap space」操作系統
解決:根據內存映像分析工具分析是內存泄漏(Memory Leak)仍是內存溢出(Memory Overflow)。線程
若是是內存泄漏就經過工具查看泄露對象到GC Roots的引用鏈。定位出泄漏代碼。設計
若是不存在泄漏,應當檢查虛擬機的堆參數(-Xms與-Xmx),看是否能夠調大。code
二、虛擬機棧和本地方法棧溢出對象
HotSpot虛擬機中並不區分虛擬機棧和和本地方法棧,所以-Xoss參數(設置本地方法棧大小)存在,但其實是無效的,棧容量只由-Xss參數設定。
關於虛擬機棧和本地方法棧,java虛擬機規範中描述了兩種異常:
若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常。
若是虛擬機在動態擴展棧時沒法申請到足夠的內存空間,則拋出OutOfMemoryError異常。
這兩種狀況存在着一些互相重疊的地方:當棧空間沒法繼續分配時,究竟是內存過小,仍是已使用的棧空間太大,其本質上只是對同一件事情的兩種描述而已。在單線程的操做中,不管是因爲棧幀太大,仍是虛擬機棧空間過小,當棧空間沒法分配時,虛擬機拋出的都是 StackOverflowError 異常,而不會獲得 OutOfMemoryError 異常。而在多線程環境下,則會拋出 OutOfMemoryError 異常。
緣由其實不難理解,操做系統分配給每一個進程的內存是有限制的,譬如 32 位的 Windows 限制爲 2GB。虛擬機提供了參數來控制 Java 堆和方法區的這兩部份內存的最大值。剩餘的內存爲 2GB(操做系統限制)減去 Xmx(最大堆容量),再減去 MaxPermSize(最大方法區容量),程序計數器消耗內存很小,能夠忽略掉。若是虛擬機進程自己耗費的內存不計算在內,剩下的內存就由虛擬機棧和本地方法棧「瓜分」了。每一個線程分配到的棧容量越大,能夠創建的線程數量天然就越少,創建線程時就越容易把剩下的內存耗盡。
遇到OOM這時候咱們應該怎麼作:若是是創建過多線程致使的內存溢出,在不能減小線程數或者更換 64 位虛擬機的狀況下,就只能經過減小最大堆和減小棧容量來換取更多的線程。若是沒有這方面的經驗,這種經過「減小內存」的手段來解決內存溢出的方式會比較難以想到。
3.方法區和運行時常量池溢出
在jdk1.6及以前的版本中,因爲常量池分配在永久代內,咱們能夠經過-XX:PermSize和-XX:MaxPermSize限制方法區大小,從而間接限制其中常量池的容量。
經過String.intern()方法試驗。
jdk1.6環境經過-XX:PermSize和-XX:MaxPermSize限制方法區大小,會產生java.lang.OutOfMemoryError: PermGen space錯誤。
jdk1.7環境經過-Xms和 -Xmx限制堆大小,會產生 java.lang.OutOfMemoryError: Java heap space錯誤。
intern方法:當調用該方法時,會在運行時常量池查看有無該字符串,若是有的話,直接返回該引用(這個字符串在運行時常量池中的地址);若是沒有的話,在運行時常量池中存放一份數據,並返回該字符串在運行時常量池中的地址。這都是在JDK1.6及以前發生的事情。若是是1.7以後,若是發如今運行時常量池中沒有的話,在運行時常量池中存放的是一份這個字符串String的引用(即這個String對象在堆中的地址),並返回該引用的值。
當class文件被加載(load)到JVM時會將常量池中的內容存放在運行時常量池(在perm區中,即永久代)中。上面 這一句只是針對JDK1.6及以前的版本適用。JDK1.7以後對方法區進行了一些優化。1.7以後將運行時常量池移到了堆中()。緣由,Perm 區是一個類靜態的區域,主要存儲一些加載類的信息,常量池,方法片斷等內容,默認大小隻有4M。一旦超過範圍,會直接產生java.lang.OutOfMemoryError: PermGen space錯誤。
方法區溢出也是一種常見的內存溢出異常,一個類要被垃圾器回收掉,斷定條件是比較嚴苛的。在常常動態產生大量Class的應用中(會產生java.lang.OutOfMemoryError: PermGen space錯誤),須要特別注意類的回收情況。這類場景除了上面提到的程序使用了CGLib字節碼加強和動態語言以外,常見的還有:大量JSP或者動態產生JSP文件的應用、基於OSGi的應用等。
4.本機直接內存溢出
DirectMemory容量能夠經過-XX:MaxDirectMemorySize指定,若是不指定,則默認與Java堆最大值(-Xmx指定)同樣。代碼清單越過了DirectByteBuffer類,直接經過反射獲取Unsafe實例進行內存分配(Unsafe類的getUnsafe方法限制了只有引導類加載器纔會返回實例,也就是設計者但願只有rt.jar中的類才能使用Unsafe的功能)。由於,雖然使用DirectByteBuffer分配內存也會拋出內存異常,但它拋出異常時並無真正向操做系統申請內存分配,而是經過計算得知內存沒法分配,因而手動拋出異常,真正申請分配內存的方法是unsafe.allocateMemory.
/** * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M */ public class DirectMemoryOOM { private static final int _1MB = 1024*1024; public static void main(String[] args) throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while(true) { unsafe.allocateMemory(_1MB); } } }
由DirectMemory致使的內存溢出,一個明顯的特徵是在Heap Dump文件中不會看見明顯的異常,若是讀者發現OOM以後Dump文件很小,而程序中又直接或者間接使用了NIO,那就能夠考慮檢查一下是否是這方面的緣由。