《深入理解Java虛擬機(jī)》學(xué)習(xí)筆記系列-運(yùn)行時數(shù)據(jù)區(qū)(概念掃盲)(一)

1 導(dǎo)讀

經(jīng)常會看到JVM內(nèi)存模型,其實(shí)說的就是JVM運(yùn)營時數(shù)據(jù)區(qū)的一個結(jié)構(gòu),這篇文章主要記錄了對不同JDK版本運(yùn)營時數(shù)據(jù)區(qū)的學(xué)習(xí)做一個總結(jié)。

2 運(yùn)行時數(shù)據(jù)區(qū)

2.1 什么是運(yùn)行時數(shù)據(jù)區(qū)

Java虛擬機(jī)在執(zhí)行程序的過程中,為了方便對程序進(jìn)行內(nèi)存管理,會將他管理的內(nèi)存區(qū)域劃分為幾個不同作用的區(qū)域。JVM運(yùn)行時數(shù)據(jù)區(qū)包含以下圖中幾個區(qū)域,其中堆、方法區(qū)是不同線程共享的,而程序計數(shù)器、本地方法棧、虛擬機(jī)棧是線程私有的。

運(yùn)行時數(shù)據(jù).png

2.2 運(yùn)行時數(shù)據(jù)區(qū)介紹

2.2.1 程序計數(shù)器

簡單來講,程序計數(shù)器是程序執(zhí)行當(dāng)前字節(jié)碼的位置信息,每一個線程都有私有的程序計數(shù)器,這樣就可以保證每個線程可以獨(dú)立運(yùn)行自己的代碼,減少與其他線程的交互,提高代碼執(zhí)行效率。

在《Java虛擬機(jī)規(guī)范》中,如果當(dāng)前執(zhí)行的不是本地方法,那么記錄的是當(dāng)前指令的地址;如果執(zhí)行的是本地方法,那么這個值是未指定的(Undefind),這塊區(qū)域很大,足以放下指令指針和本地指針。

2.2.2 虛擬機(jī)棧

每一個Java線程都有一個私有的虛擬機(jī)棧,與線程生命周期同步。當(dāng)每個方法被執(zhí)行的時候,Java虛擬機(jī)會同步創(chuàng)建一個棧幀,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每個方法被調(diào)用到調(diào)用結(jié)束,就對應(yīng)一個棧幀從入棧到出棧。

public class JVMTest {
?
 public static void main(String[] args) {
?
 int i = 0;
 String str = "abs";
 JVMTest jvmTest = new JVMTest();
 String m = jvmTest.m(str);
 System.out.println(m);
 }
?
 private String m(String m) {
?
 return m;
 }
?
 private static String m2(String m2) {
 return m2;
 }
}
  • 局部變量表

    局部變量表是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。

    下面圖片是main方法的局部變量表,第一個局部變量是形參0-args,后面依次是 1-i、2-str、3-jvmTest、4-m變量

局部變量表.png
  • 操作數(shù)棧

    下面是main方法的字節(jié)碼指令

     0 iconst_0 #將int類型常量值0壓入棧
     1 istore_1 #將int類型值存入局部變量1
     2 ldc #2 <a> #把常量池中的項壓入棧
     4 astore_2 #將引用類型或returnAddress類型值存入局部變量2
     5 new #3 <com/hp/test/thread/JVMTest> #創(chuàng)建一個JVMTest新對象
     8 dup #復(fù)制棧頂部一個字長內(nèi)容
     9 invokespecial #4 <com/hp/test/thread/JVMTest.<init>> #調(diào)用初始化方法(之后會在類加載中詳細(xì)講解init方法)
    12 astore_3 #將引用類型或returnAddress類型值存入局部變量3
    13 aload_3 #從局部變量中裝載引用類型值
    14 aload_2 #從局部變量中裝載引用類型值
    15 invokespecial #5 <com/hp/test/thread/JVMTest.m> #調(diào)用m方法
    18 astore 4 #將引用類型或returnAddress類型值存入局部變量4
    20 getstatic #6 <java/lang/System.out> #從類中獲取靜態(tài)字段
    23 aload 4 #從局部變量中裝在引用類型值
    25 invokevirtual #7 <java/io/PrintStream.println> #調(diào)用println方法
    28 return #從方法中返回,返回值為void

下面是m方法的字節(jié)碼指令

    0 aload_1 #從局部變量1中裝載引用類型值
    1 areturn #從方法中返回引用類型的數(shù)據(jù)

那么問題來了,明明方法m中形參是第一個局部變量,為什么是aload_1而不是aload_0?

這是因為如果當(dāng)前幀是由構(gòu)造方法或者實(shí)例方法創(chuàng)建的,那么該對象引用this將會存在index為0 的Slot處,非靜態(tài)方法,都會創(chuàng)建this的一個參數(shù),index為0,其余的參數(shù)是按照順序排放的,static 方法被不可以使用this是因為static方法中沒有放this的index。

再來看一下m2方法字節(jié)碼指令

    0 aload_0
    1 areturn
  • 方法出口

    通俗的講,每一個方法在執(zhí)行完或者拋出異常都需要回到上一個方法調(diào)用的地方,這就是方法出口。

  • 動態(tài)鏈接

    通俗的講,我們在方法調(diào)用時并不是直接將方法的信息保存在同一個棧幀中,而是指向該方法在常量池中的地址引用。

2.2.3 本地方法棧

? 本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別只是虛擬機(jī) 棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的本地(Native) 方法服務(wù)。

2.2.4 方法區(qū)

? 它用于存儲已被虛擬機(jī)加載 的類型信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼緩存等數(shù)據(jù)。我們平時操作new對象時所需要的類信息就存儲在方法區(qū)。(之后會在類加載中詳細(xì)講解)

? 在JDK1.8以前,方法區(qū)存放的位置可以理解成永久代,但是到了JDK1.8,用元數(shù)據(jù)區(qū)(metaspace)取代了永久代。永久代是需要分配內(nèi)存大小的,而元數(shù)據(jù)區(qū)在不指定大小的情況下,受限于物理內(nèi)存

2.2.5 堆

? 對于Java程序來說,堆是JVM管理內(nèi)存最大的一部分,此內(nèi)存部分是用于存放對象實(shí)例。根據(jù)對象的年齡,可以分為年輕代老年代。在年輕代中又可以分為Eden區(qū),Survive區(qū),之后要講到的GC也主要是針對堆進(jìn)行展開。

3 總結(jié)

想要對JVM有更深入的理解,必須先搞清楚每個數(shù)據(jù)區(qū)的作用,以及代碼是如何在各個數(shù)據(jù)區(qū)串聯(lián)起來的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容