模擬Java內存溢出


本文通過修改虛擬機啟動參數,來剖析常見的java內存溢出異常(基於jdk1.8)。

修改虛擬機啟動參數Java堆溢出虛擬機棧溢出方法區溢出本機直接內存溢出

修改虛擬機啟動參數

  這里我們使用的是IDEA集成開發環境,選擇Run/Debug Configurations


  然后選擇Configuration,修改VM options配置,就可以修改虛擬機啟動參數了,本文的示例代碼doc注釋部分將會給出需要設置的虛擬機參數。

Java堆溢出

 1import java.util.ArrayList;
2import java.util.List;
3
4/**
5 * 堆溢出測試.
6 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
7 * -XX:HeapDumpPath=/Users/lijl/Desktop
8 *
9 * @author jialin.li
10 * @date 2020-04-08 10:02
11 */

12public class HeapOOM {
13    public static void main(String[] args) {
14        List<HeapOOM> list = new ArrayList<>();
15
16        while (true) {
17            list.add(new HeapOOM());
18        }
19    }
20}

  這里簡單解釋一下代碼,我們通過-xms20m -Xmx20m兩個參數,限制了Java堆的大小為20MB,不可擴展,后兩個參數控制了當出現了OutOfMemoryError時,會Dump出當前內存的堆轉儲快照,並保存到指定位置中。
  接下來我們可以使用jdk自帶的VisualVM來打開快照文件。
  命令行輸入jvisualvm,點擊左上角的裝入,選中我們dump出來的堆快照文件。


  經過重新加載的堆內存記錄如下:

  這里可以很直觀的看出,OutOfMemoryError產生的原因,是HeapOOM這個對象導致的。
  解決問題的思路是:首先我們要排除 內存泄露,即我們不需要的對象沒有被回收掉。我們要找到 泄漏的對象是如何與GC Root進行關聯的?從而准確定位出泄漏代碼的位置,然后進行修改。
  如果不是內存泄漏,即堆中的對象必須存活,這個時候,我們可以通過調節虛擬機的堆參數(-Xms -Xmx),適當調大堆內存。但是在此之前,我們一定要檢查一下 代碼是否存在優化的空間,如:是否存在某些對象的生命周期過長?是否可以使用享元模式減少對象數量?等等

 

虛擬機棧溢出

 1/**
2 * 棧溢出測試.
3 * VM Args: Xss128k
4 *
5 * @author jialin.li
6 * @date 2020-04-08 10:02
7 */

8public class StackSOF {
9
10    private static int stackLength = 1;
11
12    public static void main(String[] args) {
13        stackLeak();
14    }
15
16    private static void stackLeak(){
17        try{
18            stackLength++;
19            stackLeak();
20        }catch (StackOverflowError e){
21            System.out.println("stack Length:" +stackLength);
22            e.printStackTrace();
23        }
24    }
25}

  StackOverflowError屬於比較好排查的一種錯誤,有錯誤棧可以閱讀,大部分出現這種錯誤,都是遞歸程序書寫的問題,沒有弄清楚什么時候需要return;結束遞歸。


  這里有一個有趣的現象,操作系統給每個進程分配的內存是有限的,在多線程的場景下,如果每個線程分配的棧內存過大,就會導致OOM,這個時候可以適當減少每個線程的棧內存,來解決溢出問題(這可能不是最好的辦法,只是因為這是一種比較不符合直覺的解決問題方式,所以這里單獨說一下)。

 

方法區溢出

  方法區用來存放Class相關的信息,比如類名、訪問修飾符、常量池、字段描述等等。我們可以用運行時產生的大量的類填滿方法區,這里我們使用了gclib來操作字節碼,maven坐標如下:

1<!-- cglib -->
2<dependency>
3    <groupId>cglib</groupId>
4    <artifactId>cglib</artifactId>
5    <version>2.2.2</version>
6</dependency>

代碼:

 1import net.sf.cglib.proxy.Enhancer;
2import net.sf.cglib.proxy.MethodInterceptor;
3
4/**
5 * 方法區溢出測試.
6 * VM Args: -XX:PermSize=10k -XX:MaxPermSize=10k
7 *
8 * @author jialin.li
9 * @date 2020-04-08 14:18
10 */

11public class JavaMethodAreaOOM {
12    public static void main(String[] args) {
13        while (true){
14            Enhancer enhancer = new Enhancer();
15            enhancer.setSuperclass(OOMObject.class);
16            enhancer.setUseCache(false);
17            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o, args));
18        }
19    }
20
21    static class OOMObject{
22
23    }
24}

  如果你用的也是和我一樣的jdk1.8,此時我們將沒有辦法得到OOM,因為在jdk1.8之后,PermGen已經被移除了,所以永久代的參數也被一同移除。方法區的靜態變量和常量池並入堆中,而類的元信息放到元空間中,元空間是一塊本地內存,所以它的最大可分配空間就是系統內存的最大可用空間。
  我們可以將參數改為:VM Args: -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m,再執行上述代碼,發現即使在設置了元空間大小的情況下,仍然不會觸發OOM,可見元空間可以有效解決方法區OOM問題(會觸發元空間的垃圾回收策略)。

本機直接內存溢出

  這種情況發生的比較少,直接內存的容量,我們可以通過-XX:MaxDirectMemorySize來指定,如果不指定,默認與堆的最大值一樣。我們可以通過Unsafe提供的方法申請本地內存分配。

 1import sun.misc.Unsafe;
2import java.lang.reflect.Field;
3
4/**
5 * 本機內存溢出.
6 * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
7 *
8 * @author jialin.li
9 * @date 2020-04-08 15:53
10 */

11public class DirectMemoryOOM {
12    private static final int _1MB = 1024 * 1024;
13
14    public static void main(String[] args) throws IllegalAccessException {
15        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
16        unsafeField.setAccessible(true);
17        Unsafe unsafe = (Unsafe) unsafeField.get(null);
18        while (true) {
19            unsafe.allocateMemory(_1MB);
20        }
21    }
22}

  如果你是IDE集成開發環境,可能會因為內存不足結束執行程序。

1Process finished with exit code 137 

  最后,期待您的訂閱和點贊,專欄每周都會更新,希望可以和您一起進步,同時也期待您的批評與指正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM