天天看點

Java記憶體溢出(OOM)分析

當JVM記憶體不足時,會抛出java.lang.OutOfMemoryError.

主要的OOM類型右:

Java heap space:堆空間不足

GC overhead limit exceeded : GC開銷超出限制

Permgen space:永久代記憶體不足

Metaspace:元空間記憶體不足

Unable to create new native thread:無法建立新的本地線程

Out of swap space? : 交換空間記憶體不足

Kill process or sacrifice child

Java heap space:堆空間不足

通用解決方案:通過-Xmx設定更大的堆記憶體【該方式可能隻是延遲報錯的發生,如果不能從根本上找到原因,報錯還是可能會發生】

進一步原因分析及解決方案:

流量/資料量峰值 : 可以考慮添加機器資源,或者做限流

記憶體洩漏 : 需要找到持有的對象,修改代碼

建立了一個超大對象(通常是一個大數組) : 可以進行業務切分

代碼示例

記憶體洩漏【-Xmx10m】

package oom;

import java.util.HashMap;
import java.util.Map;

/**
* 記憶體洩露
*/
public class JavaHeapSpace2 {

    public static void main(String[] args) {
        Map<Key,String> map = new HashMap<>();
        while (true) {
            Key key = new Key();
            if(!map.containsKey(key)) {
                map.put(key, "Java Overhead");
                System.out.println(key);
            }
        }

    }
}

class Key { }

View Code      
oom.Key@2ef70cb4
......
oom.Key@457298d0
oom.Key@484b94f2
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.HashMap.resize(HashMap.java:704)
    at java.util.HashMap.putVal(HashMap.java:663)
    at java.util.HashMap.put(HashMap.java:612)
    at oom.JavaHeapSpace2.main(JavaHeapSpace2.java:16)

View Result      

建立了一個超大對象

package oom;

import java.lang.management.ManagementFactory;
import java.util.List;

public class JavaHeapSpace {

    private static final int SIZE = 12 * 1024 * 2014;

    public static void main(String[] args) {
        List<String> inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
        System.out.println("JVM Arguments : " + inputArguments);
        int[] arr = new int[SIZE];
    }
}

View Code      
JVM Arguments : [-Xmx12m, -Dfile.encoding=GBK]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at oom.JavaHeapSpace.main(JavaHeapSpace.java:13)

Process finished with exit code 1

View Result[-Xmx12m]      
JVM Arguments : [-Xmx35m,-Dfile.encoding=GBK]

Process finished with exit code 0

View Result[-Xmx35m]      

GC overhead limit exceeded : GC開銷超出限制

預設情況下,當應用程式花費超過98%的時間用來做GC并且回收了不到2%的堆記憶體時,會抛出java.lang.OutOfMemoryError:GC overhead limit exceeded錯誤。

此類問題的原因與解決方案跟 Java heap space 非常類似,可以參考上文

代碼示範【使用預設的VM配置】

package oom;

import java.util.HashMap;
import java.util.Map;

public class JavaHeapSpace2 {

    public static void main(String[] args) {
        Map<Key,String> map = new HashMap<>();
        while (true) {
            Key key = new Key();
            if(!map.containsKey(key)) {
                map.put(key, "Java Overhead");
                System.out.println(key);
            }
        }
    }
}

class Key { }

View Code      
oom.Key@61f7f66c
oom.Key@1da844d
......
oom.Key@792b37e7
oom.Key@3d8151c0Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.nio.CharBuffer.wrap(CharBuffer.java:373)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)

View Result      

Permgen space:永久代記憶體不足

背景:永久代主要存儲類的資訊,比如:類加載引用、運作時常量池、字段、方法等。是以,Permgen的大小取決于被加載類的數量及類的大小。

原因:

加載了太多的類

加載了超大類

注意:JDK8已經完全移除永久代空間,取而代之的是元空間(Metaspace)

Metaspace:元空間記憶體不足

背景:Metaspace存儲類的中繼資料資訊

原因:

加載了太多的類

加載了超大類

解決方案

調整-XX:MaxMetaspaceSize參數

删除-XX:MaxMetaspaceSize參數,解除限制【預設是沒有限制的。預設情況下,對于64位伺服器端JVM,MetaspaceSize預設大小是21M(初始限制值),一旦達到這個限制值,FullGC将被觸發進行類解除安裝,并且這個限制值将會被重置,新的限制值依賴于Metaspace的剩餘容量。如果沒有足夠空間被釋放,這個限制值将會上升。】

代碼示範【JDK1.8】

package oom;

import javassist.CannotCompileException;
import javassist.ClassPool;

import java.lang.management.ManagementFactory;
import java.util.List;

public class Metaspace{

    public static void main(String[] args) throws CannotCompileException {
        List<String> inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
        System.out.println("JVM Arguments : " + inputArguments);

        for (int i = 0; i < 100000000; i++) {
            ClassPool.getDefault().makeClass("User" + i).toClass();
        }
    }
}

View Code      
JVM Arguments : [-XX:MaxMetaspaceSize=35m, -Dfile.encoding=GBK]
Exception in thread "main" javassist.CannotCompileException: by java.lang.OutOfMemoryError: Metaspace
    at javassist.ClassPool.toClass(ClassPool.java:1099)
    at javassist.ClassPool.toClass(ClassPool.java:1042)
    at javassist.ClassPool.toClass(ClassPool.java:1000)
    at javassist.CtClass.toClass(CtClass.java:1224)
    at oom.Permgen.main(Permgen.java:16)
Caused by: java.lang.OutOfMemoryError: Metaspace
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at javassist.ClassPool.toClass2(ClassPool.java:1112)
    at javassist.ClassPool.toClass(ClassPool.java:1093)
    ... 4 more

View Result      

Unable to create new native thread:無法建立新的本地線程

背景:每個線程都需要一定的記憶體空間,當JVM向底層作業系統請求建立一個新的native線程時,如果沒有足夠的資源配置設定就會報這個錯誤

原因分析及解決方案:

線程數超過了OS最大線程數ulimit限制 : 調高 OS 層面的線程最大數 - 執行 ulimia-a 檢視最大線程數限制,使用 ulimit-u xxx 調整最大線程數限制

線程數超過了本地線程最大數:限制線程池大小 ;

native記憶體不足 :

使用 -Xss 參數減少線程棧的大小

更新配置,為機器提供更多的記憶體

代碼示例

package oom;

public class UnableCreateThread {

    public static void main(String[] args) {
        while (true) {
            new Thread(() -> {
                try {
                    Thread.sleep(100000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

View Code      
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006a674447, pid=18648, tid=0x0000000000067b68
#
# JRE version: Java(TM) SE Runtime Environment (8.0_171-b11) (build 1.8.0_171-b11)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# V  [jvm.dll+0x214447][thread 424944 also had an error]
An unrecoverable stack overflow has occurred.
[thread 424964 also had an error]
An unrecoverable stack overflow has occurred.
[thread 424984 also had an error]
[thread 424992 also had an error]
An unrecoverable stack overflow has occurred.
[thread 424988 also had an error]
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:717)
    at oom.UnableCreateThread.main(UnableCreateThread.java:14)

View Result      

Out of swap space? : 交換空間記憶體不足

背景:虛拟記憶體(Virtual Memory)由實體記憶體(Physical Memory)和交換空間(Swap Space)兩部分組成。在JVM請求的總記憶體大于可用實體記憶體的情況下,作業系統會将記憶體中的資料交換到磁盤上去。當交換空間也将耗盡時就會報 Outof swap space? 錯誤。

原因分析及解決方案:【往往是由作業系統級别的問題引起的】

作業系統配置的交換空間不足 :

加交換空間【對交換空間運作垃圾回收算法會使GC暫停的時間增加幾個數量級,是以使用增加交換空間的方法】

更新機器以包含更多記憶體

系統上的另一個程序消耗所有記憶體資源:如果應用部署在JVM需要同其他程序激烈競争擷取資源的實體機上,建議将服務隔離到單獨的虛拟機中

本地記憶體洩漏導緻應用程式失敗: 優化應用程式以減少其記憶體占用

Kill process or sacrifice child

背景:作業系統是建立在程序的概念之上,這些程序在核心中作業,其中有一個非常特殊的程序,名叫“記憶體殺手(Out of memory killer)”。當核心檢測到系統記憶體不足時,OOM killer被激活,然後選擇一個程序殺掉。

原因分析:程式占用大量系統記憶體導緻其他程序沒有可用記憶體

解決方案: