Java虛擬機是由一個規(guī)范來定義的抽象計算機。
? 抽象的規(guī)范
? 具體的實現
? 運行中的虛擬機實例
每個java程序都運行于它自己的虛擬機實例中。
Java虛擬機的結構分為:類裝載子系統(tǒng),運行時數據區(qū),執(zhí)行引擎,本地方法接口。其中運行時數據區(qū)又分為:方法區(qū),堆,Java棧,PC寄存器,本地方法棧。
每個java虛擬機實例都有一個方法區(qū)和一個堆,由該虛擬機實例中所有線程所共享,對方法區(qū)的訪問必須設計成線程安全。
Java程序在運行時創(chuàng)建的所有類實例或數組放與此。每創(chuàng)建一個JVM實例就有一個堆,該實例的所有線程都共享這個堆。
Java對象中包含的基本數據由它所屬的類及其所有超類聲明的實例變量組成。只要有對對象引用,虛擬機就必須快速定位對象實例的數據。另外,他也必須能通過該對象引用訪問相應的類數據(存儲于方法區(qū)的類型信息)。因此在對象中通常會有一個指向方法區(qū)的指針。
一種可能的堆空間設計就是,把堆分為兩部分:一個句柄池,一個對象池。一個對象引用就是一個指向句柄池的本地指針。句柄池的每個條目有兩部分:一個指向實例變量的指針,一個指向方法區(qū)類型數據的指針。這種方法有利碎片整理。但每次訪問對象實例變量都得進行兩次指針轉換。
另一個設計方式是使對象指針直接指向一組數據,而該數據包括對象實例數據以及指向方法區(qū)中的類數據指針。因此只需一個指針就可以訪問對象的實例數據。當虛擬機為了減少內存碎片而移動對象時,必須在整個運行時數據區(qū)中更新指向被移動對象的引用。
每個對象都可以有一個方法表,可以加快調用實例方法時的效率。Java虛擬機規(guī)范并不要求必須有。
方法區(qū)中存放被裝載的類型信息,該類型中的類(靜態(tài))變量同樣也是存儲在方法區(qū)中。方法區(qū)大小可以在堆中自由分配,方法區(qū)可以被垃圾回收。
類型信息包括:
? 這個類型的全限定名。
? 這個類型的直接超類的全限定名(除非是java.lang.Object,無超類)
? 這個類型是類類型還是接口類型。
? 這個類型的訪問修飾符(public,abstract ...)
? 任何直接超接口的全限定名的有序列表
除了上面列出的基本類型信息外,虛擬機還為每個被裝載的類型存儲以下信息:
? 該類型的常量池
? 字段信息
? 方法信息
? 除了常量以外所有類(靜態(tài))變量
? 一個到類ClassLoader的引用
? 一個到Class類的引用
每創(chuàng)建一個線程,都將有該線程自己的PC寄存器及一個java棧,每個線程只能訪問自己的PC寄存器和java棧,該線程如果正在執(zhí)行一個java方法,則PC寄存器的值是下一條被執(zhí)行的指令;java棧使用幀為單位保存線程的運行狀態(tài)。
Java棧存儲每個線程中Java方法調用的狀態(tài)—包括局部變量,被調用時傳進來的參數,返回值,運算中間結果。
每當線程調用一個Java方法時,虛擬機都會在該線程的Java棧中壓入一個新幀。而這個新幀成為當前幀。在執(zhí)行這個方法時,它使用這個幀來存儲參數、局部變量、中間運算結果等等數據。Java棧不連續(xù),可指定初始大小和最大最小值。
Java方法可以以兩種方式完成:正常返回return;通過拋出異常而中止。
Java虛擬機的兩種類裝載器:啟動類裝載器和用戶自定義類裝載器。主要任務有:
? 裝載---查找并裝載類型的二進制數據
? 連接---執(zhí)行驗證,準備,以及解析(可選)
驗證:確保被導入類型的正確性
準備:為類變量分配內存,并將其初始化為默認值
解析:把類型中的符號引用換為直接引用
? 初始化---把類變量初始化為正確的初始值
每個Java虛擬機都必須實現一個啟動類裝載器。
用戶自定義類裝載器,盡管用戶自定義類裝載器是Java程序的一部分,但類ClassLoader中的四個方法是通往Java虛擬機的通道:
protected final Class<?> defineClass(String name, byte[] b, int off, int len);
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain);
protected final Class<?> findSystemClass(String name);
protected final void resolveClass(Class<?> c);
任何java虛擬機實現都必須把這些方法連到內部的類裝載器子系統(tǒng)中。
兩個重載的defineClass() 方法,接受一個byte[] b的字節(jié)數組作為輸入參數,在b[off]到b[off + len]之間的二進制數據必須符合java class文件格式,class文件即線性二進制數據流。name參數指定類型的全限定名,前一個defineClass將給類型賦以默認的保護域,后一個類型保護域由protectionDomain指定。
findSystemClass()使用系統(tǒng)類裝載器裝載指定類型。
resolveClass()對class實例表示的類型進行連接,defineClass()只是對類型進行裝載