一、堆內存溢出java
堆內存中主要存放對象、數組等,只要不斷地建立這些對象,而且保證 GC Roots 到對象之間有可達路徑來避免垃圾收集回收機制清除這些對象,當這些對象所佔空間超過最大堆容量時,就會產生 OutOfMemoryError 的異常。堆內存異常示例以下:數據庫
/**
設置最大堆最小堆:-Xms20m -Xmx20m
運行時,不斷在堆中建立OOMObject類的實例對象,且while執行結束以前,GC Roots(代碼中的oomObjectList)到對象(每個OOMObject對象)之間有可達路徑,垃圾收集器就沒法回收它們,最終致使內存溢出。
*/
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> oomObjectList = new ArrayList<>();
while (true) {
oomObjectList.add(new OOMObject());
}
}
}
複製代碼
運行後會報異常,在堆棧信息中能夠看到:數組
java.lang.OutOfMemoryError: Java heap space 的信息,說明在堆內存空間產生內存溢出的異常。bash
新產生的對象最初分配在新生代,新生代滿後會進行一次 Minor GC,若是 Minor GC 後空間不足會把該對象和新生代知足條件的對象放入老年代,老年代空間不足時會進行 Full GC,以後若是空間還不足以存放新對象則拋出 OutOfMemoryError 異常。網絡
常見緣由:內存中加載的數據過多如一次從數據庫中取出過多數據;集合對對象引用過多且使用完後沒有清空;代碼中存在死循環或循環產生過多重複對象;堆內存分配不合理;網絡鏈接問題、數據庫問題等。架構
二、虛擬機棧/本地方法棧溢出ide
(1)StackOverflowError:當線程請求的棧的深度大於虛擬機所容許的最大深度,則拋出StackOverflowError,簡單理解就是虛擬機棧中的棧幀數量過多(一個線程嵌套調用的方法數量過多)時,就會拋出StackOverflowError異常。學習
最多見的場景就是方法無限遞歸調用,以下:ui
/**
設置每一個線程的棧大小:-Xss256k
運行時,不斷調用doSomething()方法,main線程不斷建立棧幀併入棧,致使棧的深度愈來愈大,最終致使棧溢出。
*/
public class StackSOF {
private int stackLength=1;
public void doSomething(){
stackLength++;
doSomething();
}
public static void main(String[] args) {
StackSOF stackSOF=new StackSOF();
try {
stackSOF.doSomething();
}catch (Throwable e){//注意捕獲的是Throwable
System.out.println("棧深度:"+stackSOF.stackLength);
throw e;
}
}
}
複製代碼
上述代碼執行後拋出:spa
Exception in thread "Thread-0" java.lang.StackOverflowError 的異常。
(2)OutOfMemoryError:若是虛擬機在擴展棧時沒法申請到足夠的內存空間,則拋出 OutOfMemoryError。
咱們能夠這樣理解,虛擬機中能夠供棧佔用的空間≈可用物理內存 - 最大堆內存 - 最大方法區內存,好比一臺機器內存爲 4G,系統和其餘應用佔用 2G,虛擬機可用的物理內存爲 2G,最大堆內存爲 1G,最大方法區內存爲 512M,那可供棧佔有的內存大約就是 512M,假如咱們設置每一個線程棧的大小爲 1M,那虛擬機中最多能夠建立 512個線程,超過 512個線程再建立就沒有空間能夠給棧了,就報 OutOfMemoryError 異常了。
棧上可以產生 OutOfMemoryError 的示例以下:
/**
設置每一個線程的棧大小:-Xss2m
運行時,不斷建立新的線程(且每一個線程持續執行),每一個線程對一個一個棧,最終沒有多餘的空間來爲新的線程分配,致使OutOfMemoryError
*/
public class StackOOM {
private static int threadNum = 0;
public void doSomething() {
try {
Thread.sleep(100000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final StackOOM stackOOM = new StackOOM();
try {
while (true) {
threadNum++;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
stackOOM.doSomething();
}
});
thread.start();
}
} catch (Throwable e) {
System.out.println("目前活動線程數量:" + threadNum);
throw e;
}
}
}
複製代碼
上述代碼運行後會報異常
在堆棧信息中能夠看到java.lang.OutOfMemoryError: unable to create new native thread的信息,沒法建立新的線程,說明是在擴展棧的時候產生的內存溢出異常。
總結:在線程較少的時候,某個線程請求深度過大,會報 StackOverflow 異常,解決這種問題能夠適當加大棧的深度(增長棧空間大小),也就是把 -Xss 的值設置大一些,但通常狀況下是代碼問題的可能性較大;在虛擬機產生線程時,沒法爲該線程申請棧空間了。
會報 OutOfMemoryError 異常,解決這種問題能夠適當減少棧的深度,也就是把 -Xss 的值設置小一些,每一個線程佔用的空間小了,總空間必定就能容納更多的線程,可是操做系統對一個進程的線程數有限制,經驗值在 3000~5000 左右。
在 jdk1.5 以前 -Xss 默認是 256k,jdk1.5 以後默認是 1M,這個選項對系統硬性仍是蠻大的,設置時要根據實際狀況,謹慎操做。
三、方法區溢出
前面說到,方法區主要用於存儲虛擬機加載的類信息、常量、靜態變量,以及編譯器編譯後的代碼等數據,因此方法區溢出的緣由就是沒有足夠的內存來存放這些數據。
因爲在 jdk1.6 以前字符串常量池是存在於方法區中的,因此基於 jdk1.6 以前的虛擬機,能夠經過不斷產生不一致的字符串(同時要保證和 GC Roots 之間保證有可達路徑)來模擬方法區的 OutOfMemoryError 異常;但方法區還存儲加載的類信息,因此基於 jdk1.7 的虛擬機,能夠經過動態不斷建立大量的類來模擬方法區溢出。
/**
設置方法區最大、最小空間:-XX:PermSize=10m -XX:MaxPermSize=10m
運行時,經過cglib不斷建立JavaMethodAreaOOM的子類,方法區中類信息愈來愈多,最終沒有能夠爲新的類分配的內存致使內存溢出
*/
public class JavaMethodAreaOOM {
public static void main(final String[] args){
try {
while (true){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(JavaMethodAreaOOM.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o,objects);
}
});
enhancer.create();
}
}catch (Throwable t){
t.printStackTrace();
}
}
}
複製代碼
上述代碼運行後會報:
java.lang.OutOfMemoryError: PermGen space 的異常,說明是在方法區出現了內存溢出的錯誤。
四、本機直接內存溢出
本機直接內存(DirectMemory)並非虛擬機運行時數據區的一部分,也不是 Java 虛擬機規範中定義的內存區域,但 Java 中用到 NIO 相關操做時(好比 ByteBuffer 的 allocteDirect 方法申請的是本機直接內存),也可能會出現內存溢出的異常。
JVM內存區域劃分,便於它可以更加高效的管理自身的內存。當程序中出現這種因爲JVM形成的內存溢出的狀況的時候,須要根據不一樣的狀況作不一樣的分析與處理。
歡迎長按下圖公衆號:以Java架構贏天下
公衆號後臺回覆【Java】,獲取精選Java架構學習資料(視頻+文件+項目實戰)