JVM, 中文名是java虛擬機, 正如它的名字, 是一個虛擬機器,來模擬通用的物理機。 JVM是一個標準,一套規范, 規定了.class文件在其內部運行的相關標準和規范。 及其相關的內部構成。 比如:所有的JVM都是基于棧結構的運行方式。那么不符合這種要求的,不算是JVM, 如Android中所使用的Dalvik 虛擬機就不能稱作是JAVA 虛擬機, 因為它是基于寄存器(最新的Android系統據說已經放棄了Dalvik VM, 而是使用ART)。
JVM相關的產品有很多, 通常最有名的莫過于現在Oracle公司所有的HotSpot 虛擬機。因此, 這里討論的都是HotSpot虛擬機, 如果沒有特別說明。
需要注意的是:如果一個類被不同的類加載器加載, 那么就是兩個不同的類。被java編譯器(不僅限于, 還有其他任何的可以編輯成為.class的編譯器)編譯過的.class文件(可能是以jar、war、jsp等形式), 經過類加載器加載 、 驗證、準備、解析、初始化之后, 才可以被使用。基本的過程如下:
加載: 首先,通過一個類的全類名來獲取此類的二進制字節流。其次,將類中所代表的靜態存儲結構轉換為運行時數據結構, 最后,生成一個代表加載的類的java.lang.Class對象, 作為方法區這個類的所有數據的訪問入口。加載完成之后, 虛擬機外部的二進制靜態數據結構就轉換成了虛擬機所需要的結構存儲在方法區中(至于如何轉換, 則由具體虛擬機自己定義實現), 而所生成的Class對象, 則存放在方法區中, 用來作為程序訪問方法區中數據的外部接口。驗證:其目的就是保證加載進來的.class文件不會危害到虛擬機本身, 且內容符合當前虛擬機規范要求。主要驗證的內容大致有:文件格式、元數據驗證、字節碼驗證、符號引用驗證。其中文件格式驗證, 主要確保符合class文件格式規范(如文本后綴為.class的文件將驗證不通過), 以及主次版本號, 驗證是否當前JVM可以處理等。元數據驗證,主要驗證編譯后的字節碼描述信息是否符合java語法規范。字節碼驗證, 其最為復雜, 主要通過控制流和數據流確定語義是否合法、符合邏輯。符號引用驗證,可以看做是除自身以外(常量池中各種引用符號)的信息匹配校驗,如通過持有的引用能否找到對應的實例。準備:正式為類變量分配內存,并設置類變量的初始值。這些變量都會在方法區中進行分配。解析:將常量池內的符號引用替換為直接引用的過程。主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄等。初始化:加載的最后階段, 程序真正運行的開始。
類加載進來, JVM是通過上圖所示的區域來運行和管理這些加載進來的CLASS。即程序運行的是時候, 由上面邏輯單元來運行程序, 包括:方法區、堆、本地方法棧、棧、程序計數器(PC)五大部分組成(有些VM說常量池也是其中的一個單元, 但是HotSpot VM中的常量池是方法區中的一部分)。(注意線程共享)程序計數器 (PC):可以看做是當前線程執行字節碼的行號指示器。字節碼解釋器工作的時候就是通過這個計數器的值來選取下一條需要執行的字節碼指令, 分支, 循環、跳轉、異常處理、線程恢復等基礎功能依賴計數器完成。虛擬機棧:和計數器一樣, 也是線程私有的,生命周期同線程一致。每個方法在執行時,都會創建一個棧幀,用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。方法調入則入棧, 方法執行完則出站。局部變量表存儲各種基本類型數據(java的8種,其中long,double占用2個局部變量控件,其余數據占用1個)、對象引用(reference類型)。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時, 這個方法需要在幀中分配多大的局部變量空間是完全確定的。在方法運行期間是不會改變局部變量表的大小的。本地方法棧:此棧和JVM棧作用非常類似, 不同在于本地方法棧為虛擬機使用到的Native方法服務, 而JVM棧則是為Java執行的方法服務。Sun HotSpot虛擬機, 直接把本地方法棧和虛擬機棧合二為一。本地方法棧也會拋出StackOverFlowError和OutOfMemoryError異常。Java堆:是JVM管理內存中最大的一塊。被所有線程共享一塊區域。堆是GC垃圾收集器管理的主要區域。從內存回收角度看, java堆被分為新生代、老年代, 再細致一點有其他的劃分。這些目的主要就是更快的分配和回收內存。方法區:和java堆相同, 線程共享區域, 用來存儲已被虛擬機加載的類信息, 常量、靜態變量、即時編譯器編譯后的代碼等數據。有人稱作此方法區為“永久帶”, 本質上不等價,只是HotSpot VM將GC分代收集擴展到了方法區,這樣HotSpot的垃圾收集器管理方法區和管理java堆一樣(優點:不用專門為方法區寫一套垃圾收集器, 缺點:容易導致內存溢出)。官方現在擁也有放棄永久帶并改為采用Native Memory來實現方法區的計劃,目前已經發布的JDK7中的HotSpot中, 已經將原本放在方法區中的字符串常量池移出了。
運行時常量池:是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述外,還有一項就是常量池, 用于存放編譯期間生成的各種字面量和符號引用 ,這部分內容在類加載后進入方法區的運行時常量池中存放。
垃圾收集本是有一套非常復雜的算法, 如果在方法區中(HotSpot VM中的永久帶)進行垃圾收集, 那么其性價比極底的,因為垃圾回收主要收集永久帶中的兩部分內容:廢棄的常量和無用的類。回收永久帶中的常量和方法區非常相似。但是在堆中, 尤其是在新生代中,常規應用進行一次垃圾收集, 一般可以回收70%——95%的空間。而永久帶的垃圾收集要遠地與此。如上圖所示, 每一個黑框中都是一個垃圾收集器, 對應特定的垃圾收集算法, 來挺高整體的垃圾收集效率。新聞熱點
疑難解答