java內(nèi)存溢出如何產(chǎn)生
Java是由Sun Microsystems公司推出的Java面向?qū)ο蟪绦蛟O(shè)計語言(以下簡稱Java語言)和Java平臺的總稱。下面是學習啦小編帶來的關(guān)于java內(nèi)存溢出如何產(chǎn)生的內(nèi)容,歡迎閱讀!
java內(nèi)存溢出如何產(chǎn)生:
java虛擬機規(guī)范規(guī)定的java虛擬機內(nèi)存其實就是java虛擬機運行時數(shù)據(jù)區(qū),其架構(gòu)如下:
其中方法區(qū)和堆是由所有線程共享的數(shù)據(jù)區(qū)。
Java虛擬機棧,本地方法棧和程序計數(shù)器是線程隔離的數(shù)據(jù)區(qū)。
Java官方定義: http://www.98ki.com/servlet/HomeServlet?method=get&id=53
Java各內(nèi)存區(qū)域分析: http://www.98ki.com/servlet/HomeServlet?method=get&id=43
通過分析各個區(qū)域的內(nèi)容我們分別寫出各個區(qū)域的內(nèi)存溢出實例
堆溢出
由Java的官方文檔我們可以看出,Java堆中存放:對象、數(shù)組。下面以不斷創(chuàng)建對象為例:
Exception in thread "main"java.lang.OutOfMemoryError: Java heap space
public class HeapLeak {
public static void main(String[] args){
ArrayList list = new ArrayList ();
while ( true ){
list.add( new HeapLeak.method()) ;
}
}
static class method{
}
}
棧溢出
從Java官方API中我們知道,棧中存儲:基本數(shù)據(jù)類型,對象引用,方法等。下面以無限遞歸創(chuàng)建方法和申請??臻g為例,分別演示棧的stackOverflow和OutOfMemory
l Exception in thread "main" java.lang.StackOverflowError
package Memory;
public class StackLeak {
public static void main(String[] args){
method ();
}
public static void method (){
method ();
}
}
l Exception in thread "main"java.lang.OutOfMemoryError: unable to create new native thread
package Memory;
public class StackOutOfMemory {
public static int count = 1;
public void noStop() {
while ( true ) {
}
}
public void newThread() {
while ( true ) {
Thread t = new Thread( new Runnable() {
public void run() {
System. out .println( " 已創(chuàng)建第 " + count +++ " 個線程 " );
noStop();
}
});
t.start();
}
}
public static void main(String[] args){
new StackOutOfMemory().newThread();
}
}
Java hotspot虛擬機中一個線程占用內(nèi)存可通過-Xss設(shè)置,而能創(chuàng)建的線程數(shù)計算方法為:
可創(chuàng)建線程數(shù)=(物理內(nèi)存-Os預留內(nèi)存-堆內(nèi)存-方法區(qū)內(nèi)存)/單個線程大小
在測試的時候這里還有點小插曲,電腦強關(guān)了一次,因為把-Xss設(shè)置成了2M,內(nèi)存使用增加到97%左右,操作系統(tǒng)死了,這個進程不斷在創(chuàng)建線程,但是并沒有因為內(nèi)存不足而停下來,直到電腦完全死掉也沒有報出錯誤信息。最后分析是因為電腦空閑內(nèi)存還有600M,在線程還沒有創(chuàng)建完的時候,已經(jīng)開啟的線程太多,在死之前大概能開到200多個,對內(nèi)存大量消耗,造成系統(tǒng)掛掉。
這里又出現(xiàn)一個有趣的現(xiàn)象,當線程順序創(chuàng)建到第88個的時候,count跳了很多,并且開始無序,有興趣的可以深入學習一下線程方面的問題,我也會在后面的博客分析這個問題。
而換成200M的時候,創(chuàng)建第二個線程的時候就報了OutOfMemory.不管Xss設(shè)置多少,報錯之后,程序都會一直走下去,執(zhí)行已開線程中的任務(wù)。
常量池溢出
從Java官方API中我們知道,常量區(qū)代表運行時每個class文件中的常量表。它包括幾種常量:編譯期的數(shù)字常量、方法或者域的引用(在運行時解析)。runtime constant pool的功能類似于傳統(tǒng)編程語言的符號表,盡管它包含的數(shù)據(jù)比典型的符號表要豐富的多。
下面以不斷添加Stirng為例:
Exception in thread "main"java.lang.OutOfMemoryError: PermGen space
常量池在方法區(qū)中,首先設(shè)置持久代大小,使其不可擴展。
然后需要做的就不停地往方法區(qū)中加字符串。其中intern()就是查看方法區(qū)中有沒有這個字符串,沒有的話就加進去,如果這里不用intern(),字符串是存在堆里的,會報heapOutOfMemory.
這里需要注意的是,在 HotSpot 中,方法區(qū)是在堆的持久代中的。
package Memory;
import java.util.ArrayList;
public class ConstantPoolLeak {
public static void main(String[] args) {
int count = 0;
ArrayList list = new ArrayList ();
while ( true )
list.add(String. valueOf (count++).intern()) ;
}
}
方法區(qū)溢出
從Java官方API中我們知道,方法區(qū)存放每個Class的結(jié)構(gòu),比如說運行時常量池、域、方法數(shù)據(jù)、方法體、構(gòu)造函數(shù)、包括類中的專用方法、實例初始化、接口初始化。
Java的反射和動態(tài)代理可以動態(tài)產(chǎn)生Class,另外第三方的CGLIB可以直接操作字節(jié)碼,也可以動態(tài)產(chǎn)生Class,下面通過CGLIB來演示。
import java.lang.reflect.Method;
public class MethodAreaLeak {
public static void main(String[] args){
while ( true ){
Enhancer enhancer = new Enhancer ();
enhancer.setSuperClass(OOMObject. class );
enhancer.setUseCache( false );
enhancer.setCallback( new MethodInterceptor (){
public Object intercept(Object obj, Method method,Object[] args,
MethodProxy proxy) throws Throwable{
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
class OOMObject{
}
}
本機直接內(nèi)存溢出
Java虛擬機可以通過參數(shù)-XX:MaxDirectMemorySize設(shè)定本機直接內(nèi)存可用大小,如果不指定,則默認與java堆內(nèi)存大小相同。JDK中可以通過反射獲取Unsafe類(Unsafe的getUnsafe()方法只有啟動類加載器Bootstrap才能返回實例)直接操作本機直接內(nèi)存。通過使用-XX:MaxDirectMemorySize=10M,限制最大可使用的本機直接內(nèi)存大小為10MB,例子代碼如下
package Memory;
import java.lang.reflect.Field;
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 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 直接想操作系統(tǒng)申請內(nèi)存
unsafe.allocateMemory( _1MB );
}
}
}
相關(guān)閱讀推薦:
看了java內(nèi)存溢出如何產(chǎn)生文章內(nèi)容的人還看: