深入理解Java虛擬機系列——JVM的基本結構

    首先,當一個程序啓動之前,它的class會被類裝載器裝入方法區執行引擎讀取方法區的字節碼自適應解析,邊解析就邊運行(其中一種方式),然後pc寄存器指向了main函數所在位置,虛擬機開始爲main函數在java棧中預留一個棧幀(每個方法都對應一個棧幀),然後開始跑main函數,main函數裏的代碼被執行引擎映射成本地操作系統裏相應的實現,然後調用本地方法接口,本地方法運行的時候,操縱系統會爲本地方法分配本地方法棧,用來儲存一些臨時變量,然後運行本地方法,調用操作系統APIi等等。  

爲了消化上面的那個圖,我們先來分析一下下面這張圖

爲什麼jvm的內存是分佈在操作系統的堆中呢??

    因爲操作系統的棧是操作系統管理的,它隨時會被回收,所以如果jvm放在棧中,那java的一個null對象就很難確定會被誰回收了,那gc的存在就一點意義都莫有了,而要對棧做到自動釋放也是jvm需要考慮的,所以放在堆中就最合適不過了

jvm的內存結構居然和操作系統的結構驚人的一致,區別在哪??

    原來jvm的設計的模型其實就是操作系統的模型,基於操作系統的角度,jvm就是個java.exe/javaw.exe,也就是一個應用,而基於class文件來說,jvm就是個操作系統,而jvm的方法區,也就相當於操作系統的硬盤區,我爲什麼喜歡叫他permanent區嗎,因爲這個單詞是永久的意思,也就是永久區,我們的磁盤就是不斷電的永久區嘛,是一樣的意思啊。而java棧和操作系統棧是一致的,無論是生長方向還是管理的方式,至於堆嘛,雖然概念上一致目標也一致,分配內存的方式也一直(new,或者malloc等等),但是由於他們的管理方式不同,jvm是gc回收,而操作系統是程序員手動釋放,所以在算法上有很多的差異,gc的回收算法,估計是jvm裏面的經典啊。

pc寄存器是幹嘛用的??

    所謂pc寄存器,無論是在虛擬機中還是在我們虛擬機所寄宿的操作系統中功能目的是一致的,計算機上的pc寄存器是計算機上的硬件,本來就是屬於計算機,(這一點對於學過彙編的同學應該很容易理解,有很多的寄存器eax,esp之類的32位寄存器,jvm裏的寄存器就相當於彙編裏的esp寄存器),計算機用pc寄存器來存放“僞指令”或地址,而相對於虛擬機pc寄存器它表現爲一塊內存(一個字長,虛擬機要求字長最小爲32位)虛擬機的pc寄存器的功能也是存放僞指令,更確切的說存放的是將要執行指令的地址,它甚至可以是操作系統指令的本地地址,當虛擬機正在執行的方法是一個本地方法的時候,jvm的pc寄存器存儲的值是undefined,所以你現在應該很明確的知道,虛擬機的pc寄存器是用於存放下一條將要執行的指令的地址(字節碼流)

對於一個運行中的Java程序而言,其中的每一個線程都有它自己的PC(程序計數器)寄存器,它是在該線程啓動時創建的,PC寄存器的大小是一個字長,因此它既能夠持有一個本地指針,也能夠持有一個returnAddress(finally塊)。當線程執行某個Java方法時,PC寄存器的內容總是下一條將被執行指令的“地址”,這裏的“地址”可以是一個本地指針,也可以是在方法字節碼中相對於該方法起始指令的偏移量。如果該線程正在執行一個本地方法,那麼此時PC寄存器的值是“undefined”。

 classLoader是如何加載class文件和存儲文件信息??

    當一個classLoder啓動的時候,classLoader的生存地點在jvm中的堆,然後它會去主機硬盤上將A.class裝載到jvm的方法區,方法區中的這個字節文件會被虛擬機拿來new A字節碼(),然後在堆內存生成了一個A字節碼的對象,然後A字節碼這個內存文件有兩個引用一個指向A的class對象,一個指向加載自己的classLoader。那麼方法區中的字節碼內存塊,除了記錄一個class自己的class對象引用和一個加載自己的ClassLoader引用之外,還記錄了什麼信息呢??見下圖


1.類信息:修飾符(public final)

         是類還是接口(class,interface)

         類的全限定名(Test/ClassStruct.class)

         直接父類的全限定名(java/lang/Object.class)

         直接父接口的權限定名數組(java/io/Serializable)

   也就是 public final class ClassStruct extends Object implements Serializable這段描述的信息提取

2.字段信息:修飾符(pirvate)

           字段類型(java/lang/String.class)

           字段名(name)

           也就是類似private String name;這段描述信息的提取

3.方法信息:修飾符(public static final)

            方法返回值(java/lang/String.class)

            方法名(getStatic_str)

            參數需要用到的局部變量的大小還有操作數棧大小(操作數棧我們後面會講)

            方法體的字節碼(就是花括號裏的內容)

            異常表(throws Exception)

       也就是對方法public static final String getStatic_str ()throws Exception的字節碼的提取
 4.常量池:

  4.1.直接常量:

     1.1CONSTANT_INGETER_INFO整型直接常量池public final int CONST_INT=0;

     1.2CONSTANT_String_info字符串直接常量池   public final Stri CONST_STR="CONST_STR";

     1.3CONSTANT_DOUBLE_INFO浮點型直接常量池

     等等各種基本數據類型基礎常量池(待會我們會反編譯一個類,來查看它的常量池等。)

  4.2.方法名、方法描述符、類名、字段名,字段描述符的符號引用

     也就是所有編譯器能夠被確定,能夠被快速查找的內容都存放在這裏,它像數組一樣通過索引訪問,就是專門用來做查找的。編譯時就能確定數值的常量類型都會複製它的所有常量到自己的常量池中,或者嵌入到它的字節碼流中。作爲常量池或者字節碼流的一部分,編譯時常量保存在方法區中,就和一般的類變量一樣。但是當一般的類變量作爲他們的類型的一部分數據而保存的時候,編譯時常量作爲使用它們的類型的一部分而保存

  5.類變量:

     就是靜態字段( public static String static_str="static_str";)

     虛擬機在使用某個類之前,必須在方法區爲這些類變量分配空間。

  6.一個到classLoader的引用,通過this.getClass().getClassLoader()來取得爲什麼要先經過class呢?思考一下,看一下上面的圖,再回來思考。(class A 對象擁有A字節碼和加載它的加載器地址引用)

  7.一個到class對象的引用,這個對象存儲了所有這個字節碼內存塊的相關信息。所有你能夠看到的區域,比如:類信息,你可以通過this.getClass().getName()取得所有的方法信息,可以通過this.getClass().getDeclaredMethods(),字段信息可以通過this.getClass().getDeclaredFields(),等等,所有在字節碼中你想得到的,調用的,通過class這個引用基本都能夠幫你完成。因爲他就是字節碼在內存塊在堆中的一個對象

   8.方法表,如果學習c++的人應該都知道c++的對象內存模型有一個叫虛表的東西,java本來的名字就叫c++- -,它的方法表其實說白了就是c++的虛表,它的內容就是這個類的所有實例可能被調用的所有實例方法的直接引用。也是爲了動態綁定的快速定位而做的一個類似緩存的查找表,它以數組的形式存在於內存中。不過這個表不是必須存在的,取決於虛擬機的設計者,以及運行虛擬機的機器是否有足夠的內存。

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