Java方法區

方法區 
在一個jvm實例的內部,類型信息被存儲在一個稱爲方法區的內存邏輯區中。類型信息是由類加載器在類加載時從類文件中提取出來的。類(靜態)變量也存儲在方法區中。 

jvm實現的設計者決定了類型信息的內部表現形式。如,多字節變量在類文件是以big-endian存儲的,但在加載到方法區後,其存放形式由jvm根據不一樣的平臺來具體定義。 

jvm在運行應用時要大量使用存儲在方法區中的類型信息。在類型信息的表示上,設計者除了要儘量提升應用的運行效率外,還要考慮空間問題。根據不一樣的需求,jvm的實現者能夠在時間和空間上追求一種平衡。 

由於方法區是被全部線程共享的,因此必須考慮數據的線程安全。假如兩個線程都在試圖找lava的類,在lava類尚未被加載的狀況下,只應該有一個線程去加載,而另外一個線程等待。 

方法區的大小沒必要是固定的,jvm能夠根據應用的須要動態調整。一樣方法區也沒必要是連續的。方法區能夠在堆(甚至是虛擬機本身的堆)中分配。jvm能夠容許用戶和程序指定方法區的初始大小,最小和最大尺寸。 

方法區一樣存在垃圾收集,由於經過用戶定義的類加載器能夠動態擴展Java程序,一些類也會成爲垃圾。jvm能夠回收一個未被引用類所佔的空間,以使方法區的空間最小。 

類型信息 
對每一個加載的類型,jvm必須在方法區中存儲如下類型信息: 
一 這個類型的完整有效名 
二 這個類型直接父類的完整有效名(除非這個類型是interface或是 
    java.lang.Object,兩種狀況下都沒有父類) 
三 這個類型的修飾符(public,abstract, final的某個子集) 
四 這個類型直接接口的一個有序列表 

類型名稱在java類文件和jvm中都以完整有效名出現。在java源代碼中,完整有效名由類的所屬包名稱加一個".",再加上類名 
組成。例如,類Object的所屬包爲java.lang,那它的完整名稱爲java.lang.Object,但在類文件裏,全部的"."都被 
斜槓「/」代替,就成爲java/lang/Object。完整有效名在方法區中的表示根據不一樣的實現而不一樣。 

除了以上的基本信息外,jvm還要爲每一個類型保存如下信息: 
類型的常量池( constant pool) 
域(Field)信息 
方法(Method)信息 
除了常量外的全部靜態(static)變量 

常量池 
jvm爲每一個已加載的類型都維護一個常量池。常量池就是這個類型用到的常量的一個有序集合,包括實際的常量(string, 
integer, 和floating point常量)和對類型,域和方法的符號引用。池中的數據項象數組項同樣,是經過索引訪問的。 
由於常量池存儲了一個類型所使用到的全部類型,域和方法的符號引用,因此它在java程序的動態連接中起了核心的做用。 

域信息 
jvm必須在方法區中保存類型的全部域的相關信息以及域的聲明順序, 
域的相關信息包括: 
域名 
域類型 
域修飾符(public, private, protected,static,final   volatile, transient的某個子集) 
        
方法信息 
jvm必須保存全部方法的如下信息,一樣域信息同樣包括聲明順序 
方法名 
方法的返回類型(或 void) 
方法參數的數量和類型(有序的) 
方法的修飾符(public, private, protected, static, final, synchronized, native, abstract的一個子集)除了abstract和native方法外,其餘方法還有保存方法的字節碼(bytecodes)操做數棧和方法棧幀的局部變量區的大小           
異常表 

類變量( 
  Class Variables 
  譯者:就是類的靜態變量,它只與類相關,因此稱爲類變量 
) 
類變量被類的全部實例共享,即便沒有類實例時你也能夠訪問它。這些變量只與類相關,因此在方法區中,它們成爲類數據在邏輯上的一部分。在jvm使用一個類以前,它必須在方法區中爲每一個non-final類變量分配空間。 

常量(被聲明爲final的類變量)的處理方法則不一樣,每一個常量都會在常量池中有一個拷貝。non-final類變量被存儲在聲明它的 
類信息內,而final類被存儲在全部使用它的類信息內。 

對類加載器的引用 
jvm必須知道一個類型是由啓動加載器加載的仍是由用戶類加載器加載的。若是一個類型是由用戶類加載器加載的,那麼jvm會將這個類加載器的一個引用做爲類型信息的一部分保存在方法區中。 

jvm在動態連接的時候須要這個信息。當解析一個類型到另外一個類型的引用的時候,jvm須要保證這兩個類型的類加載器是相同的。這對jvm區分名字空間的方式是相當重要的。 

對Class類的引用 
jvm爲每一個加載的類型(譯者:包括類和接口)都建立一個java.lang.Class的實例。而jvm必須以某種方式把Class的這個實例和存儲在方法區中的類型數據聯繫起來。 

你能夠經過Class類的一個靜態方法獲得這個實例的引用// A method declared in class java.lang.Class: 
public static Class forName(String className); 

假如你調用forName("java.lang.Object"),你會獲得與java.lang.Object對應的類對象。你甚至能夠經過這個函數 
獲得任何包中的任何已加載的類引用,只要這個類可以被加載到當前的名字空間。若是jvm不能把類加載到當前名字空間, 
forName就會拋出ClassNotFoundException。 
(譯者:熟悉COM的朋友必定會想到,在COM中也有一個稱爲      類對象(Class Object)的東東,這個類對象主要      是實現一種工廠模式,而java因爲有了jvm這個中間      層,類對象能夠很方便的提供更多的信息。這兩種類對象      都是Singleton的) 

也能夠經過任一對象的getClass()函數獲得類對象的引用,getClass被聲明在Object類中: 
// A method declared in class java.lang.Object: 
public final Class getClass(); 
例如,假如你有一個java.lang.Integer的對象引用,能夠激活getClass()獲得對應的類引用。 

經過類對象的引用,你能夠在運行中得到相應類存儲在方法區中的類型信息,下面是一些Class類提供的方法: 
// Some of the methods declared in class java.lang.Class: 
public String getName(); 
public Class getSuperClass(); 
public boolean isInterface(); 
public Class[] getInterfaces(); 
public ClassLoader getClassLoader(); 

這些方法僅能返回已加載類的信息。getName()返回類的完整名,getSuperClass()返回父類的類對象,isInterface()判斷是不是接口。getInterfaces()返回一組類對象,每一個類對象對應一個直接父接口。若是沒有,則返回一個長度爲零的數組。 
getClassLoader()返回類加載器的引用,若是是由啓動類加載器加載的則返回null。全部的這些信息都直接從方法區中得到。 

方法表 
爲了提升訪問效率,必須仔細的設計存儲在方法區中的數據信息結構。除了以上討論的結構,jvm的實現者還能夠添加一些其餘的數據結構,如方法表。jvm對每一個加載的非虛擬類的類型信息中都添加了一個方法表,方法表是一組對類實例方法的直接引用(包括從父類繼承的方法)。jvm能夠經過方法錶快速激活實例方法。(譯者:這裏的方法表與C++中的虛擬函數表同樣,但java方法全都 是virtual的,天然也不用虛擬二字了。正像java宣稱沒有      指針了,其實java裏全是指針。更安全只是加了更完備的檢查機制,但這都是以犧牲效率爲代價的,我的認爲java的設計者      始終是把安全放在效率之上的,全部java才更適合於網絡開發) 

一個例子 
爲了顯示jvm如何使用方法區中的信息,咱們據一個例子,咱們 
看下面這個類: 
class Lava { 
    private int speed = 5; // 5 kilometers per hour 
    void flow() { 
    } 
} 

class Volcano { 
    public static void main(String[] args) { 
        Lava lava = new Lava(); 
        lava.flow(); 
    } 
} 
下面咱們描述一下main()方法的第一條指令的字節碼是如何被執行的。不一樣的jvm實現的差異很大,這裏只是其中之一。 

爲了運行這個程序,你以某種方式把「Volcano"傳給了jvm。有了這個名字,jvm找到了這個類文件(Volcano.class)並讀入,它從 
類文件提取了類型信息並放在了方法區中,經過解析存在方法區中的字節碼,jvm激活了main()方法,在執行時,jvm保持了一個指向當前類(Volcano)常量池的指針。 

注意jvm在尚未加載Lava類的時候就已經開始執行了。正像大多數的jvm同樣,不會等全部類都加載了之後纔開始執行,它只會在須要的時候才加載。 

main()的第一條指令告知jvm爲列在常量池第一項的類分配足夠的內存。jvm使用指向Volcano常量池的指針找到第一項,發現是一個對Lava類的符號引用,而後它就檢查方法區看lava是否已經被加載了。 

這個符號引用僅僅是類lava的完整有效名」lava「。這裏咱們看到爲了jvm能儘快從一個名稱找到一個類,一個良好的數據結構是多麼重要。這裏jvm的實現者能夠採用各類方法,如hash表,查找樹等等。一樣的算法能夠用於Class類的forName()的實現。 

當jvm發現尚未加載過一個稱爲"Lava"的類,它就開始查找並加載類文件"Lava.class"。它從類文件中抽取類型信息並放在了方法區中。 

jvm因而以一個直接指向方法區lava類的指針替換了常量池第一項的符號引用。之後就能夠用這個指針快速的找到lava類了。而這個替換過程稱爲常量池解析(constant pool resolution)。在這裏咱們替換的是一個native指針。 

jvm終於開始爲新的lava對象分配空間了。此次,jvm仍然須要方法區中的信息。它使用指向lava數據的指針(剛纔指向volcano常量池第一項的指針)找到一個lava對象究竟須要多少空間。 

jvm總可以從存儲在方法區中的類型信息知道某類型對象須要的空間。但一個對象在不一樣的jvm中可能須要不一樣的空間,並且它的空間分佈也是不一樣的。(譯者:這與在C++中,不一樣的編譯器也有不一樣的對象模型是一個道理) 

一旦jvm知道了一個Lava對象所要的空間,它就在堆上分配這個空間並把這個實例的變量speed初始化爲缺省值0。假如lava的父對象也有實例變量,則也會初始化。 

當把新生成的lava對象的引用壓到棧中,第一條指令也結束了。下面的指令利用這個引用激活java代碼把speed變量設爲初始值,5。另一條指令會用這個引用激活Lava對象的flow()方法。
java

相關文章
相關標籤/搜索