首頁  >  文章  >  Java  >  案例解說JVM記憶體空間(建議收藏)

案例解說JVM記憶體空間(建議收藏)

Java后端技术全栈
Java后端技术全栈轉載
2023-08-15 17:16:461186瀏覽

今天,跟大家一起聊聊關於JVM記憶體空間的話題,這也是一線網路大廠面試中常被問及的問題,建議小夥伴們收藏後常常拿出來翻閱,重在理解。好了,不多說了,開始今天的正題。

JVM會把記憶體分割成不同的資料區域,那載入的類別是分配到哪裡呢?

下圖是記憶體的各個區域,包括:方法區、堆疊、虛擬機器堆疊、本機方法堆疊、程式計數器。

案例解說JVM記憶體空間(建議收藏)

方法區

#方法區用於儲存已被虛擬機載入的類別資訊、常數、靜態變數、即時編譯器編譯後的程式碼等資料。類別的載入中提到了類別載入的五個階段。在載入階段,會將位元組流所代表的靜態儲存結構轉換為方法區的執行時間資料結構,在準備階段,會將變數所使用的記憶體都會在方法區中進行分配。

程式計數器

#來一個簡單的程式碼,計算(1 2)*3並回傳

public int cal() {
    int a = 1;
    int b = 2;
    int c = 3;
    return (a + b) * c;
}

這段程式碼在載入到虛擬機器的時候,就變成了以下的字節碼,虛擬機執行的時候,就會一行行執行。

案例解說JVM記憶體空間(建議收藏)

java是多執行緒的,在執行緒切換回來後,它需要知道原先的執行位置在哪裡。用來記錄這個執行位置的,就是程式計數器,為了確保執行緒間的計數器互相不影響,這個記憶體區域是線程私有的。

虚拟机栈

虚拟机栈也是线程私有的,生命周期与线程相同。每个线程都有自己的虚拟机栈,如果这个线程执行了一个方法,就会创建一个栈帧,方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。比如下面的例子,fun1调用fun2,fun2调用fun3,fun3创建Hello对象。

public void fun1() {
    fun2();
}

public void fun2() {
    fun3();
}

public void fun3() {
    Hello hello = new Hello();
}

调用的时候,流程图如下:

案例解說JVM記憶體空間(建議收藏)

执行完成的时候,流程图如下:

案例解說JVM記憶體空間(建議收藏)

每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。局部变量主要是存放方法参数以及方法内部定义的局部变量,操作数栈是一个后入先出栈,当方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。

我们通过上面(1+2)*3的例子,把方法区、程序计数器、虚拟机栈的协同工作理一下。首先通过javap查看它的字节码,经过类加载器加载后,此时这个字节码存在方法区中。stack表示栈深度是2,locals是本地变量的slot个数,args_size是入参的个数,默认是this。栈的深度、本地变量个数,入参个数,都是在编译器决定的。

案例解說JVM記憶體空間(建議收藏)

如下图,指令的位置是方法区,局部变量和操作数栈的位置是虚拟机栈,程序计数器就在程序计数器(这个下面的图就不再重复)。当执行偏地址为0的指令的时候,程序计数器为0,局部变量第一个值是this,当前的指令就是方法区0:iconst_1,指令iconst_1就是把int常量值1进栈,这个1就到了虚拟机栈的操作数栈中。

案例解說JVM記憶體空間(建議收藏)

当执行偏地址为1的指令的时候,程序计数器为1,把操作数栈的值赋值到局部变量,此时操作数栈清空了,局部变量多了一个1,这条指令执行完,就是对应上面int a=1的语句。

案例解說JVM記憶體空間(建議收藏)

另外b,c兩個語句的賦值,對應2,3,4,5指令,這邊不再重複。執行完5後,如下圖:

案例解說JVM記憶體空間(建議收藏)

執行6的時候,是執行iload_1,就是把第二個int型局部變數壓入堆疊頂,這裡的變數是1。

案例解說JVM記憶體空間(建議收藏)

執行7的時候,是執行iload_2,就是把第三個int型局部變數壓入堆疊頂,這裡的變數是2。

案例解說JVM記憶體空間(建議收藏)

執行8的時候,是iadd語句,指的是棧頂的兩個int型元素出棧,得到結果後再壓入棧頂。

案例解說JVM記憶體空間(建議收藏)

執行9的時候,把堆疊頂的元素3,賦值到第五個局部變數。

案例解說JVM記憶體空間(建議收藏)

執行到11的時候,把第五個局部變數值壓入棧頂,執行到13的時候,把第四個局部變數值壓入棧頂,執行14的時候,棧頂的兩個int型元素出棧,相乘後的結果入棧,執行15的時候,從目前方法返回目前棧頂int型元素。這些與上面的相加差不多,就不再贅述了。

堆記憶體區域的唯一目的就是要存放物件實例,幾乎所有的物件實例都在這裡分配記憶體。例如上面的fun1呼叫fun2,fun2呼叫fun3,fun3建立Hello物件。 fun3方法中建立物件時,就是在堆中創建的,並且把位址賦值給fun3的局部變數。 Java堆中還可細分為:新生代與老年代;新生代也細分為Eden空間、From Survivor空間、To Survivor空間。

案例解說JVM記憶體空間(建議收藏)

總結

#整體流程如下,先把java文件編譯成class文件,透過類別載入器載入到方法區。執行緒呼叫方法的時候,會創建一個棧幀,讀取方法區的字節碼執行指令,執行指令的時候,會把執行的位置記錄在程式計數器中,如果創建對象,會在堆內存中創建,方法執行完,這個棧幀就會出棧。

案例解說JVM記憶體空間(建議收藏)

#相關參數

#-XX:PermSize:永久代記憶體容量。

-XX:MaxPermSize:永久代最大記憶體容量。

-XX:MetaspaceSize :元空間初始值的大小

##-XX:MaxMetaspaceSize :元空間最大值大小

-XX:CompressedClassSpaceSize :元空間中儲存Klass類元資料部分的空間大小

-Xss:堆疊記憶體容量。

-Xms:堆記憶體容量。

-Xmx:堆疊最大記憶體容量,通常和-Xms設定一樣,防止執行時間擴容產生的影響。

-Xmn:新生代記憶體容量,老年代就是堆記憶體容量-新生代記憶體容量

-XX:SurvivorRatio=8:新生代還細分為Eden空間、From Survivor空間、To Survivor空間,設定為8代表Eden空間:From Survivor空間:To Survivor空間=8:1:1,例如新生代有10M,那Eden空間佔8M,From Survivor空間、To Survivor空間各佔1M。

案例解說JVM記憶體空間(建議收藏)

以上是案例解說JVM記憶體空間(建議收藏)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:Java后端技术全栈。如有侵權,請聯絡admin@php.cn刪除