工作中最常見的六種OOM問題
前言
今天接著線上問題這個話題,跟大家一起聊聊線上服務出現OOM問題的6種場景,希望對你會有所幫助。
1.堆內存OOM
堆內存OOM是最常見的OOM了。
出現堆內存OOM問題的異常信息如下:
java.lang.OutOfMemoryError: Java heap space
此OOM是由于JVM中heap的最大值,已經不能滿足需求了。
舉個例子:
public class HeapOOMTest {
public static void main(String[] args) {
List<HeapOOMTest> list = Lists.newArrayList();
while (true) {
list.add(new HeapOOMTest());
}
}
}
這里創建了一個list集合,在一個死循環中不停往里面添加對象。
執行結果:
出現了java.lang.OutOfMemoryError: Java heap space的堆內存溢出。
很多時候,excel一次導出大量的數據,獲取在程序中一次性查詢的數據太多,都可能會出現這種OOM問題。
我們在日常工作中一定要避免這種情況。
2.棧內存OOM
有時候,我們的業務系統創建了太多的線程,可能會導致棧內存OOM。
出現堆內存OOM問題的異常信息如下:
java.lang.OutOfMemoryError: unable to create new native thread
給大家舉個例子:
public class StackOOMTest {
public static void main(String[] args) {
while (true) {
new Thread().start();
}
}
}
使用一個死循環不停創建線程,導致系統產生了大量的線程。
執行結果:
如果實際工作中,出現這個問題,一般是由于創建的線程太多,或者設置的單個線程占用內存空間太大導致的。
建議在日常工作中,多用線程池,少自己創建線程,防止出現這個OOM。
3.棧內存溢出
我們在業務代碼中可能會經常寫一些遞歸
調用,如果遞歸的深度超過了JVM允許的最大深度,可能會出現棧內存溢出問題。
出現棧內存溢出問題的異常信息如下:
java.lang.StackOverflowError
例如:
public class StackFlowTest {
public static void main(String[] args) {
doSamething();
}
private static void doSamething() {
doSamething();
}
}
執行結果:
出現了java.lang.StackOverflowError棧溢出的錯誤。
我們在寫遞歸代碼時,一定要考慮遞歸深度。即使是使用parentId一層層往上找的邏輯,也最好加一個參數控制遞歸深度。防止因為數據問題導致無限遞歸的情況,比如:id和parentId的值相等。
4.直接內存OOM
直接內存不是虛擬機運行時數據區的一部分,也不是《Java虛擬機規范》中定義的內存區域。
它來源于NIO,通過存在堆中的DirectByteBuffer操作Native內存,是屬于堆外內存,可以直接向系統申請的內存空間。
出現直接內存OOM問題時異常信息如下:
java.lang.OutOfMemoryError: Direct buffer memory
例如下面這樣的:
public class DirectOOMTest {
private static final int BUFFER = 1024 * 1024 * 20;
public static void main(String[] args) {
ArrayList<ByteBuffer> list = new ArrayList<>();
int count = 0;
try {
while (true) {
// 使用直接內存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
list.add(byteBuffer);
count++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
System.out.println(count);
}
}
}
執行結果:
會看到報出來java.lang.OutOfMemoryError: Direct buffer memory直接內存空間不足的異常。
5.GC OOM
GC OOM是由于JVM在GC時,對象過多,導致內存溢出,建議調整GC的策略。
出現GC OOM問題時異常信息如下:
java.lang.OutOfMemoryError: GC overhead limit exceeded
為了方便測試,我先將idea中的最大和最小堆大小都設置成10M:
-Xmx10m -Xms10m
例如下面這個例子:
public class GCOverheadOOM {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
});
}
}
}
執行結果:
出現這個問題是由于JVM在GC的時候,對象太多,就會報這個錯誤。
我們需要改變GC的策略。
在老代80%時就是開始GC,并且將-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)設置的更合理。
6.元空間OOM
JDK8之后使用Metaspace來代替永久代,Metaspace是方法區在HotSpot中的實現。
Metaspace不在虛擬機內存中,而是使用本地內存也就是在JDK8中的ClassMetadata,被存儲在叫做Metaspace的native memory。
出現元空間OOM問題時異常信息如下:
java.lang.OutOfMemoryError: Metaspace
為了方便測試,我修改一下idea中的JVM參數,增加下面的配置:
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
指定了元空間和最大元空間都是10M。
接下來,看看下面這個例子:
public class MetaspaceOOMTest {
static class OOM {
}
public static void main(String[] args) {
int i = 0;
try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOM.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, args);
}
});
enhancer.create();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
執行結果:
程序最后會報java.lang.OutOfMemoryError: Metaspace的元空間OOM。
這個問題一般是由于加載到內存中的類太多,或者類的體積太大導致的。
好了,今天的內容先分享到這里,下一篇文章重點給大家講講,如何用工具定位OOM問題,敬請期待。