Java 堆 OutOfMemoryError
Java 堆是用來存儲對象實例的, 因此如果我們不斷地創(chuàng)建對象, 并且保證 GC Root 和創(chuàng)建的對象之間有可達路徑以免對象被垃圾回收, 那么當創(chuàng)建的對象過多時, 會導致 heap 內存不足, 進而引發(fā) OutOfMemoryError 異常。
public class OutOfMemoryErrorTest{
public static void main (String[] args){
List《Integer》 list = new ArryList《》();
int i=0;
while(true){
list.add(i++);
}}}
上面是一個引發(fā) OutOfMemoryError 異常的代碼, 我們可以看到, 它就是通過不斷地創(chuàng)建對象, 并將對象保存在 list 中防止其被垃圾回收, 因此當對象過多時, 就會使堆內存溢出。
通過 java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError 我們設置了堆內存為 10 兆, 并且使用參數(shù) -XX:+HeapDumpOnOutOfMemoryError 讓 JVM 在發(fā)生 OutOfMemoryError 異常時打印出當前的內存快照以便于后續(xù)分析。
編譯運行上述代碼后, 會有如下輸出:
》》》java -Xms10m - Xms10m-XX:HeapDumpOnOutOfMemoryError com.test.OutOfMemoryErrorTest 16-10-12 10:28
java.lang.OutOfMemoryError:Java heap space
Dumping heap to java_pid1810.hprof.。。
Heap dump file created [14212861 bytes in 0.128 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)
堆內存溢出的時候,虛擬機會拋出java.lang.OutOfMemoryError:java heap space,出現(xiàn)此種情況的時候,我們需要根據(jù)內存溢出的時候產生的dump文件來具體分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm啟動參數(shù))。出現(xiàn)此種問題的時候有可能是內存泄露,也有可能是內存溢出了。
如果內存泄露,我們要找出泄露的對象是怎么被GC ROOT引用起來,然后通過引用鏈來具體分析泄露的原因。
如果出現(xiàn)了內存溢出問題,這往往是程序本生需要的內存大于了我們給虛擬機配置的內存,這種情況下,我們可以采用調大-Xmx來解決這種問題。
下面我們通過如下的代碼來演示一下此種情況的溢出:
[java] view plain copyimport java.util.*;
import java.lang.*;
public class OOMTest{
public static void main(String.。。 args){
List《byte[]》 buffer = new ArrayList《byte[]》();
buffer.add(new byte[10*1024*1024]);
}
}
我們通過如下的命令運行上面的代碼:
java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest
程序輸入如下的信息:
?。踦lain] view plain copy[GC 1180K-》366K(19456K), 0.0037311 secs]
[Full GC 366K-》330K(19456K), 0.0098740 secs]
?。跢ull GC 330K-》292K(19456K), 0.0090244 secs]
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at OOMTest.main(OOMTest.java:7)
從運行結果可以看出,JVM進行了一次Minor gc和兩次的Major gc,從Major gc的輸出可以看出,gc以后old區(qū)使用率為134K,而字節(jié)數(shù)組為10M,加起來大于了old generation的空間,所以拋出了異常,如果調整-Xms21M,-Xmx21M,那么就不會觸發(fā)gc操作也不會出現(xiàn)異常了。
Java 棧 StackOverflowError
我們知道, JVM 的運行時數(shù)據(jù)區(qū)中有一個叫做 虛擬機棧 的內存區(qū)域, 此區(qū)域的作用是: 每個方法在執(zhí)行時都會創(chuàng)建一個棧幀, 用于存儲局部變量表, 操作數(shù)棧, 方法出口等信息。
因此我們可以創(chuàng)建一個無限遞歸的遞歸調用, 當遞歸深度過大時, 就會耗盡??臻g, 進而導致了 StackOverflowError 異常。
下面是具體的代碼:
public class OutOfMemoryErrorTest{
public static void main (String [] srgs){
stackOutOfMemoryError(1);
?。?/p>
public static void stackOutOfMemoryError(int depth){
depth++;
stackOutOfMemoryError(depth);
}}
當編譯運行上述的代碼后, 會輸出如下異常信息:
Exception in thread “main” java.lang.StackOverflowError
at com.test.OutOfMemoryErrorTest.stackOutOfMemoryError(OutOfMemoryErrorTest.java:27)
棧溢出拋出java.lang.StackOverflowError錯誤,出現(xiàn)此種情況是因為方法運行的時候棧的深度超過了虛擬機容許的最大深度所致。
出現(xiàn)這種情況,一般情況下是程序錯誤所致的,比如寫了一個死遞歸,就有可能造成此種情況。 下面我們通過一段代碼來模擬一下此種情況的內存溢出。
?。踛ava] view plain copyimport java.util.*;
import java.lang.*;
public class OOMTest{
public void stackOverFlowMethod(){
stackOverFlowMethod();
}
public static void main(String.。。 args){
OOMTest oom = new OOMTest();
oom.stackOverFlowMethod();
}
}
運行上面的代碼,會拋出如下的異常:
?。踦lain] view plain copyException in thread “main” java.lang.StackOverflowError
at OOMTest.stackOverFlowMethod(OOMTest.java:6)
通過上面的實驗其實也從側面驗證了一個結論:當對象大于新生代剩余內存的時候,將直接放入老年代,當老年代剩余內存還是無法放下的時候,出發(fā)垃圾收集,收集后還是不能放下就會拋出內存溢出異常了
持久帶溢出(OutOfMemoryError: PermGen space)
我們知道Hotspot jvm通過持久帶實現(xiàn)了Java虛擬機規(guī)范中的方法區(qū),而運行時的常量池就是保存在方法區(qū)中的,因此持久帶溢出有可能是運行時常量池溢出,也有可能是方法區(qū)中保存的class對象沒有被及時回收掉或者class信息占用的內存超過了我們配置。當持久帶溢出的時候拋出java.lang.OutOfMemoryError: PermGen space。
我在工作可能在如下幾種場景下出現(xiàn)此問題。
使用一些應用服務器的熱部署的時候,我們就會遇到熱部署幾次以后發(fā)現(xiàn)內存溢出了,這種情況就是因為每次熱部署的后,原來的class沒有被卸載掉。
如果應用程序本身比較大,涉及的類庫比較多,但是我們分配給持久帶的內存(通過-XX:PermSize和-XX:MaxPermSize來設置)比較小的時候也可能出現(xiàn)此種問題。
一些第三方框架,比如spring,hibernate都通過字節(jié)碼生成技術(比如CGLib)來實現(xiàn)一些增強的功能,這種情況可能需要更大的方法區(qū)來存儲動態(tài)生成的Class文件。
我們知道Java中字符串常量是放在常量池中的,String.intern()這個方法運行的時候,會檢查常量池中是否存和本字符串相等的對象,如果存在直接返回對常量池中對象的引用,不存在的話,先把此字符串加入常量池,然后再返回字符串的引用。那么我們就可以通過String.intern方法來模擬一下運行時常量區(qū)的溢出。下面我們通過如下的代碼來模擬此種情況:
[java] view plain copyimport java.util.*;
import java.lang.*;
public class OOMTest{
public static void main(String.。。 args){
List《String》 list = new ArrayList《String》();
while(true){
list.add(UUID.randomUUID().toString().intern());
}
}
}
我們通過如下的命令運行上面代碼:
java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest
運行后的輸入如下圖所示:
?。踦lain] view plain copyException in thread “main” java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at OOMTest.main(OOMTest.java:8)
通過上面的代碼,我們成功模擬了運行時常量池溢出的情況,從輸出中的PermGen space可以看出確實是持久帶發(fā)生了溢出,這也驗證了,我們前面說的Hotspot jvm通過持久帶來實現(xiàn)方法區(qū)的說法。
OutOfMemoryError:unable to create native thread
最后我們在來看看java.lang.OutOfMemoryError:unable to create natvie thread這種錯誤。 出現(xiàn)這種情況的時候,一般是下面兩種情況導致的:
程序創(chuàng)建的線程數(shù)超過了操作系統(tǒng)的限制。對于Linux系統(tǒng),我們可以通過ulimit -u來查看此限制。
給虛擬機分配的內存過大,導致創(chuàng)建線程的時候需要的native內存太少。我們都知道操作系統(tǒng)對每個進程的內存是有限制的,我們啟動Jvm,相當于啟動了一個進程,假如我們一個進程占用了4G的內存,那么通過下面的公式計算出來的剩余內存就是建立線程棧的時候可以用的內存。 線程??偪捎脙却?4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序計數(shù)器占用的內存 通過上面的公式我們可以看出,-Xmx 和 MaxPermSize的值越大,那么留給線程??捎玫目臻g就越小,在-Xss參數(shù)配置的棧容量不變的情況下,可以創(chuàng)建的線程數(shù)也就越小。因此如果是因為這種情況導致的unable to create native thread,那么要么我們增大進程所占用的總內存,或者減少-Xmx或者-Xss來達到創(chuàng)建更多線程的目的。
JVM內存區(qū)域組成
簡單的說java中的堆和棧
java把內存分兩種:一種是棧內存,另一種是堆內存
1。在函數(shù)中定義的基本類型變量和對象的引用變量都在函數(shù)的棧內存中分配;
2。堆內存用來存放由new創(chuàng)建的對象和數(shù)組
在函數(shù)(代碼塊)中定義一個變量時,java就在棧中為這個變量分配內存空間,當超過變量的作用域后,java會自動釋放掉為該變量所分配的內存空間;在堆中分配的內存由java虛擬機的自動垃圾回收器來管理
堆的優(yōu)勢是可以動態(tài)分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態(tài)分配內存的。缺點就是要在運行時動態(tài)分配內存,存取速度較慢;
棧的優(yōu)勢是存取速度比堆要快,缺點是存在棧中的數(shù)據(jù)大小與生存期必須是確定的無靈活性。
java堆分為三個區(qū):New、Old和Permanent
GC有兩個線程:
新創(chuàng)建的對象被分配到New區(qū),當該區(qū)被填滿時會被GC輔助線程移到Old區(qū),當Old區(qū)也填滿了會觸發(fā)GC主線程遍歷堆內存里的所有對象。Old區(qū)的大小等于Xmx減去-Xmn
java棧存放
棧調整:參數(shù)有+UseDefaultStackSize -Xss256K,表示每個線程可申請256k的棧空間
每個線程都有他自己的Stack
評論
查看更多