Java虛擬機體系結構
在了解jvm的結構之前,我們有必要先來了解一下操作系統的內存基本結構:
操作系統中的jvm
為什么jvm的內存是分布在操作系統的堆中呢??因為操作系統的棧是操作系統管理的,它隨時會被回收,所以如果jvm放在棧中,那java的一個null對象就很難確定會被誰回收了,那gc的存在就一點意義都沒有了,而要對棧做到自動釋放也是jvm需要考慮的,所以放在堆中就最合適不過了。
操作系統+jvm的內存簡單布局
從上圖中,你可以看到,jvm的內存結構居然和操作系統的結構驚人的一致,再來看一個圖。看我下面紅色的標注
你應該不難發現,原來jvm的設計的模型其實就是操作系統的模型,基于操作系統的角度,jvm也就是一個應用(java.exe/javaw.exe),而基于class文件來說,jvm就是個操作系統,而jvm的方法區,也就相當于操作系統的硬盤區,所以方法區也被叫做permanent區,因為這個單詞是永久的意思,也就是永久區。而java棧和操作系統棧是一致的,無論是生長方向還是管理的方式,至于堆嘛,雖然概念上一致目標也一致,分配內存的方式也一直(new,或者malloc等等),但是由于他們的管理方式不同,jvm是gc回收,而操作系統是程序員手動釋放,所以在算法上有很多的差異.
看下面的圖。
將這個圖和上面的圖對比多了什么?沒錯,多了一個pc寄存器,所謂pc寄存器,無論是在虛擬機中還是在我們虛擬機所寄宿的操作系統中功能目的是一致的,計算機上的pc寄存器是計算機上的硬件,本來就是屬于計算機,計算機用pc寄存器來存放“偽指令”或地址,而相對于虛擬機,pc寄存器它表現為一塊內存(一個字長,虛擬機要求字長最小為32位),虛擬機的pc寄存器的功能也是存放偽指令,更確切的說存放的是將要執行指令的地址,它甚至可以是操作系統指令的本地地址,當虛擬機正在執行的方法是一個本地方法的時候,jvm的pc寄存器存儲的值是undefined,所以你現在應該很明確的知道,虛擬機的pc寄存器是用于存放下一條將要執行的指令的地址(字節碼流)。
這個圖是要告訴你,當一個classLoder啟動的時候,classLoader的生存地點在jvm中的堆,然后它會去主機硬盤上將A.class裝載到jvm的方法區,方法區中的這個字節文件會被虛擬機拿來new A字節碼(),然后在堆內存生成了一個A字節碼的對象,然后A字節碼這個內存文件有兩個引用一個指向A的class對象,一個指向加載自己的classLoader
如下圖。
下面你看我貼出的一個類的基本結構。
import java.io.Serializable; public final class ClassStruct extends Object implements Serializable{//1.類信息 //2.對象字段信息 private String name; private int id; //4.常量池 public final int CONST_INT=0; public final String CONST_STR="CONST_STR"; //5.類變量區 public static String static_str="static_str"; //3.方法信息 public static final String getStatic_str ()throws Exception{ return ClassStruct.static_str; } }
你將上面的代碼注解和上面的那個字節碼碼內存塊按標號對應一下,就會發現,其實內存的字節碼塊就是完整的把你整個類裝到了內存而已。
所以各個信息段記錄的信息可以從我們的類結構中得到。
1.類型信息:也就是public final class ClassStruct extends Object implements Serializable這段描述的信息提取
修飾符(public final)
是類還是接口(class,interface)
類的全限定名(Test/ClassStruct.class)
直接父類的全限定名(java/lang/Object.class)
直接父接口的權限定名數組(java/io/Serializable)
2.字段信息:也就是類似private String name;這段描述信息的提取
修飾符(pirvate)
字段類型(java/lang/String.class)
字段名(name)
3.方法信息:也就是對方法public static final String getStatic_str ()throws Exception的字節碼的提取
修飾符(public static final)
方法返回值(java/lang/String.class)
方法名(getStatic_str)
參數需要用到的局部變量的大小還有操作數棧大小
方法體的字節碼
異常表(throws Exception)
4.常量池:
4.1.直接常量:
4.1.1CONSTANT_INGETER_INFO整型直接常量池 public final int CONST_INT=0;
4.1.2CONSTANT_String_info字符串直接常量池 public final String CONST_STR=“CONST_STR”;
4.1.3CONSTANT_DOUBLE_INFO浮點型直接常量池
等等各種基本數據類型基礎常量池
4.2.方法名、方法描述符、類名、字段名,字段描述符的符號引用
也就是所有編譯器能夠被確定,能夠被快速查找的內容都存放在這里,它像數組一樣通過索引訪問,就是專門用來做查找的。
編譯時就能確定數值的常量類型都會復制它的所有常量到自己的常量池中,或者嵌入到它的字節碼流中。作為常量池或者字節碼流的一部分,編譯時常量保存在方法區中,就和一般的類變量一樣。但是當一般的類變量作為他們的類型的一部分數據而保存的時候,編譯時常量作為使用它們的類型的一部分而保存
5.類變量:
就是靜態字段( public static String static_str=“static_str”;)
虛擬機在使用某個類之前,必須在方法區為這些類變量分配空間。
6.一個到classLoader的引用,通過this.getClass().getClassLoader()來取得為什么要先經過class呢?思考一下,然后看第七點的解釋,再回來思考
7.一個到class對象的引用,這個對象存儲了所有這個字節碼內存塊的相關信息。所以你能夠看到的區域,比如:類信息,你可以通過this.getClass().getName()取得所有的方法信息,可以通過this.getClass().getDeclaredMethods(),字段信息可以通過this.getClass().getDeclaredFields(),等等,所以在字節碼中你想得到的,調用的,通過class這個引用基本都能夠幫你完成。因為他就是字節碼在內存塊在堆中的一個對象
8.方法表,如果學習c++的人應該都知道c++的對象內存模型有一個叫虛表的東西,java本來的名字就叫c++- -,它的方法表其實說白了就是c++的虛表,它的內容就是這個類的所有實例可能被調用的所有實例方法的直接引用。也是為了動態綁定的快速定位而做的一個類似緩存的查找表,它以數組的形式存在于內存中。不過這個表不是必須存在的,取決于虛擬機的設計者,以及運行虛擬機的機器是否有足夠的內存
原文地址:http://blog.csdn.net/yfqnihao/article/details/8289363