走進JVM-認識JAVA內存區域

前言

工作確定,房子也找好了,昨天電腦桌子也到位了,從今天開始博客進入正常更新狀態。計劃四月份之前將 JVM 相關的東西寫完。

JVM 系列主要寫一些類加載、java內存區域,垃圾回收,jvm 參數配置,使用 java 工具、arthas和 linux 命令定位解決問題

JAVA內存區域

JAVA內存區域(JAVA運行時數據區)不要和 JAVA 內存模型(JMM)混淆。

JAVA內存模型(JMM)定義了Java虛擬機(JVM)在計算機內存(RAM)中的工作方式,java內存模型指的是一套規範,規範線程如何訪問內存。

20180413163825001

圖片引用 https://luoyoubao.gitbooks.io/jvm/content/javanei-cun-mo-xing/javanei-cun-mo-xing.html

圖片引用 https://luoyoubao.gitbooks.io/jvm/content/chapter1.html

JVM被分爲三個主要的子系統

  1. 類加載器子系統
  2. 運行時數據區
  3. 執行引擎

類加載子系統負責從文件系統或者網絡中加載 Class 信息,Class 信息放在方法區中。

PC 寄存器(線程私有)

java 虛擬機中每個線程都有自己的 pc 寄存器。在任意時刻,一條線程只會執行一個方法代碼。如果執行的方法不是 native,那麼 pc 寄存器就保存正在執行的字節碼指令的地址。如果是執行的是 native 方法,pc 寄存器的值是 null。PC 寄存區也是唯一一個不會拋出 OOM 異常的區域。

JAVA 虛擬機棧(線程私有)

每條線程都有自己的虛擬機棧,這個棧和線程同時創建,用於儲存局部變量或者指向堆的指針。在 Java 虛擬機規範中,如果方法遞歸調用太深會拋出 StackOverflowError 異常;當無法申請足夠的內存時也會拋出 OOM 異常。

-Xss 用於調節棧的大小。

native 方法棧(線程私有)

調用 native 方法時使用的棧,瞭解即可。當棧溢出時,拋出 StackOverflowError 異常 ;當申請內存失敗時,拋出 OOM 異常。

堆(線程共享)

堆(heap)是線程共享的區域,垃圾回收也主要回收它,我們主要也是堆打交道。-Xms 和 -Xmx 用於調整堆的大小。

方法區(線程共享)

方法區是線程共享的內存區域,它存儲 Class 的結構信息。例如,運行時常量池、字段、方法、構造函數。方法區使用的是本地內存(堆外內存),相當於在系統上申請的內存。方法區會拋出 OOM。方法區使用元數據空間來調整。

-XX:MaxMetaspaceSize: 設置,默認 -1 不限制。

-XX:MetaspaceSize:指定元空間初始空間大小。字節爲單位。

內存區域異常拋出演示

image-20210323171447455

圖片引用 https://mp.weixin.qq.com/s/hj2GcW5nHS6U8wVZM7YBFg

實際中我們主要關注的是堆、直接內存、棧、元數據區。

堆拋出 OOM

在 idea 啓動程序的時候,傳入虛擬機參數 -Xms100m -Xmx100m -XX:+HeapDumpOnOutOfMemoryError。並使用 jvisualvm 觀察堆的使用情況。

運行結果會拋出 java.lang.OutOfMemoryError: Java heap space。

HeapDumpOnOutOfMemoryError 指定了拋出異常時 dump 內存到文件中。我們可以通過分析這個文件,看那些對象佔用比較多,從而分析問題。

/**
 * @author 張攀欽
 * @date 2021-03-23-15:23
 */

public class HeapOOM {
    static class Obj {
        private byte[] a = new byte[1024 * 1024 * 10];
    }

    public static void main(String[] args) {
        final ArrayList<Object> objects = Lists.newArrayList();
        int count = 0;
        while (true) {
            try {
                objects.add(new Obj());
                System.out.println("添加了多少次" + ++count);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }
}
image-20210323154208310

棧溢出

-Xss 用於設置棧的大小。當棧調用深度過深,會拋出 StackOverflowError 異常。

-Xss512k 時,打印結果 調用深度4868

-Xss256k 時,打印結果 調用深度1889

public class StackOverflowErrorDemo {

    private int count = 0;

    public void add() {
        count++;
        add();
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        final StackOverflowErrorDemo stackOverflowErrorDemo = new StackOverflowErrorDemo();
        try {
            stackOverflowErrorDemo.add();
        } catch (Error e) {
            System.out.println("調用深度" + stackOverflowErrorDemo.getCount());
        }
    }
}

方法區溢出

方法區主要是儲存類加載的信息,我們可以通過動態代理來模擬出來。

-XX:MaxMetaspaceSize=20m 設置元空間大小。

public class MetaOOM {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        while (true) {
            try {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(IMetaService.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                        return proxy.invoke(obj, args);
                    }
                });
                enhancer.create();
            } catch (Throwable e) {
                System.err.println(e.getMessage());
            }
        }
    }
}

interface IMetaService {
    void add();
}

直接內存溢出

直接內存(Direct Memory,也是堆外內存)的容量可以通過 -XX:MaxDirectMemorySize 設置。默認值是 64m。

一般 Nio 使用了直接內存。-XX:MaxDirectMemorySize=50m

    public static void main(String[] args) {
        final ArrayList<Object> objects = new ArrayList<>();
        while (true) {
            try {
                objects.add(ByteBuffer.allocateDirect(1024 * 1024 * 10));
            } catch (Throwable e) {
                System.out.println(e.getMessage());
            }
        }
    }
image-20210323173300937


本文分享自微信公衆號 - Mflyyou(Mflyyou)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章