天天看點

php出現oom,為什麼會出現OOM?如何解決OOM

一 堆記憶體溢出

堆記憶體溢出太常見,大部分人都應該能想得到這一點,堆記憶體用來存儲對象執行個體,我們隻要不停的建立對象,并且保證GC Roots和對象之間有可達路徑避免垃圾回收,那麼在對象數量超過最大堆的大小限制後很快就能出現這個異常

寫一段代碼測試一下,設定堆記憶體大小2M

php出現oom,為什麼會出現OOM?如何解決OOM

public class HeapOOM {

public static void main(String[] args) {

List list = new ArrayList<>();

while (true) {

list.add(new HeapOOM());

}

}

}

運作代碼,很快能看見OOM異常出現,這裡的提示是Java heap space堆記憶體溢出

php出現oom,為什麼會出現OOM?如何解決OOM

一般的排查方式可以通過設定-XX: +HeapDumpOnOutOfMemoryError在發生異常時dump出目前的記憶體轉儲快照來分析,分析可以使用Eclipse Memory Analyzer(MAT)來分析,獨立檔案可以在官網下載下傳

另外如果使用的是IDEA的話,可以使用商業版JProfiler或者開源版本的JVM-Profiler,此外IDEA2018版本之後内置了分析工具,包括Flame Graph(火焰圖)和Call Tree(調用樹)功能

php出現oom,為什麼會出現OOM?如何解決OOM
php出現oom,為什麼會出現OOM?如何解決OOM

二 方法區(運作時常量池)和元空間溢出

方法區和堆一樣,是線程共享的區域,包含Class檔案資訊、運作時常量池、常量池,運作時常量池和常量池的主要差別是具備動态性,也就是不一定非要是在Class檔案中的常量池中的内容才能進入運作時常量池,運作期間也可以可以将新的常量放入池中,比如String的intern()方法

我們寫一段代碼驗證一下String.intern(),同時我們設定-XX:MetaspaceSize=50m -XX:MaxMetaspaceSize=50m 元空間大小。由于我使用的是1.8版本的JDK,而1.8版本之前方法區存在于永久代(PermGen),1.8之後取消了永久代的概念,轉為元空間(Metaspace),如果是之前版本可以設定PermSize MaxPermSize永久代的大小

private static String str = "test";

public static void main(String[] args) {

List list = new ArrayList<>();

while (true){

String str2 = str + str;

str = str2;

list.add(str.intern());

}

}

運作代碼,會發現代碼報錯

php出現oom,為什麼會出現OOM?如何解決OOM

再次修改配置,去除元空間限制,修改堆記憶體大小-Xms20m -Xmx20m,可以看見堆記憶體報錯。

php出現oom,為什麼會出現OOM?如何解決OOM

這是為什麼呢?

intern()本身是一個native方法,它的作用是:如果字元串常量池中已經包含一個等 于此String對象的字元串,則傳回代表池中這個字元串的String對象;否則,将此String對象包含的字元串添加到常量池中,并且傳回String對象的引用

而在1.7版本之後,字元串常量池已經轉移到堆區,是以會報出堆記憶體溢出的錯誤,如果1.7之前版本的話會看見PermGen space的報錯

三 直接記憶體溢出

直接記憶體并不是虛拟機運作時資料區域的一部分,并且不受堆記憶體的限制,但是受到機器記憶體大小的限制。常見的比如在NIO中可以使用native函數直接配置設定堆外記憶體就容易導緻OOM的問題

直接記憶體大小可以通過-XX:MaxDirectMemorySize指定,如果不指定,則預設與Java 堆最大值-Xmx一樣

由直接記憶體導緻的記憶體溢出,一個明顯的特征是在Dump檔案中不會看見明顯的異常,如果發現OOM之後Dump檔案很小,而程式中又直接或間接使用了NIO,那就可以考慮檢查一下是不是這方面的原因

四 棧記憶體溢出

棧是線程私有,它的生命周期和線程相同。每個方法在執行的同時都會建立一個棧幀用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊,方法調用的過程就是棧幀入棧和出棧的過程

在java虛拟機規範中,對虛拟機棧定義了兩種異常:

1.如果線程請求的棧深度大于虛拟機所允許的深度,将抛出StackOverflowError異常

2.如果虛拟機棧可以動态擴充,并且擴充時無法申請到足夠的記憶體,抛出OutOfMemoryError異常

先寫一段代碼測試一下,設定-Xss160k,-Xss代表每個線程的棧記憶體大小

public class StackOOM {

private int length = 1;

public void stackTest() {

System.out.println("stack lenght=" + length);

length++;

stackTest();

}

public static void main(String[] args) {

StackOOM test = new StackOOM();

test.stackTest();

}

}

測試發現,單線程下無論怎麼設定參數都是StackOverflow異常

php出現oom,為什麼會出現OOM?如何解決OOM

嘗試把代碼修改為多線程,調整-Xss2m,因為為每個線程配置設定的記憶體越大,棧空間可容納的線程數量越少,越容易産生記憶體溢出。反之,如果記憶體不夠的情況,可以調小該參數來達到支撐更多線程的目的

public class StackOOM {

private void dontStop() {

while (true) {

}

}

public void stackLeakByThread() {

while (true) {

new Thread(() -> dontStop()).start();

}

}

public static void main(String[] args) throws Throwable {

StackOOM stackOOM = new StackOOM();

stackOOM.stackLeakByThread();

}

}

php出現oom,為什麼會出現OOM?如何解決OOM