標籤:編譯 受限 固定 lib 輸入 程式設計語言 分離 bootstra 方向
作為一名Java使用者,掌握JVM的體繫結構也是必須的。
說起Java,人們首先想到的是Java程式設計語言,然而事實上,Java是一種技術,它由四方面組成:Java程式設計語言、Java類檔案格式、Java虛擬機器和Java應用程式介面(Java API)。它們的關係如所示:
運行期環境代表著Java平台,開發人員編寫Java代碼(.java檔案),然後將之編譯成位元組碼(.class檔案),再然後位元組碼被裝入記憶體,一旦位元組碼進入虛擬機器,它就會被解譯器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。
Java平台由Java虛擬機器和Java應用程式介面搭建,Java語言則是進入這個平台的通道,用Java語言編寫並編譯的程式可以運行在這個平台上。這個平台的結構如所示:
在Java平台的結構中, 可以看出,Java虛擬機器(JVM) 處在核心的位置,是程式與底層作業系統和硬體無關的關鍵。它的下方是移植介面,移植介面由兩部分組成:適配器和Java作業系統, 其中依賴於平台的部分稱為適配器;JVM 通過移植介面在具體的平台和作業系統上實現;在JVM 的上方是Java的基本類庫和擴充類庫以及它們的API, 利用Java API編寫的應用程式(application) 和小程式(Java applet) 可以在任何Java平台上運行而無需考慮底層平台, 就是因為有Java虛擬機器(JVM)實現了程式與作業系統的分離,從而實現了Java 的平台無關性。
JVM在它的生存周期中有一個明確的任務,那就是運行Java程式,因此當Java程式啟動的時候,就產生JVM的一個執行個體;當程式運行結束的時候,該執行個體也跟著消失了。下面我們從JVM的體繫結構和它的運行過程這兩個方面來對它進行比較深入的研究。
1、Java虛擬機器的體繫結構
·每個JVM都有兩種機制:
①類裝載子系統:裝載具有適合名稱的類或介面
②執行引擎:負責執行包含在已裝載的類或介面中的指令
·每個JVM都包含:
方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器
對於JVM的學習,在我看來這麼幾個部分最重要:
Java代碼編譯和執行的整個過程
JVM記憶體管理及記憶體回收機制
下面分別對這幾部分進行說明:
2、Java代碼編譯和執行的整個過程
也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java檔案),然後將之編譯成位元組碼(.class檔案),再然後位元組碼被裝入記憶體,一旦位元組碼進入虛擬機器,它就會被解譯器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。
(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM位元組碼(.class檔案)的過程。 流程圖如下所示:
(2)Java位元組碼的執行是由JVM執行引擎來完成,流程圖如下所示:
Java代碼編譯和執行的整個過程包含了以下三個重要的機制:
·Java源碼編譯機制
·類載入機制
·類執行機制
(1)Java源碼編譯機制
Java 源碼編譯由以下三個過程組成:
①分析和輸入到符號表
②註解處理
③語義分析和產生class檔案
流程圖如下所示:
最後產生的class檔案由以下部分組成:
①結構資訊:包括class檔案格式版本號碼及各部分的數量與大小的資訊
②中繼資料:對應於Java源碼中聲明與常量的資訊。包含類/繼承的超類/實現的介面的聲明資訊、域與方法聲明資訊和常量池
③方法資訊:對應Java源碼中語句和運算式對應的資訊。包含位元組碼、異常處理器表、求值棧與局部變數區大小、求值棧的類型記錄、偵錯符號資訊
(2)類載入機制
JVM的類載入是通過ClassLoader及其子類來完成的,類的層次關係和載入順序可以由來描述:
①Bootstrap ClassLoader
負責載入$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類
②Extension ClassLoader
負責載入java平台中擴充功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
③App ClassLoader
負責記載classpath中指定的jar包及目錄中class
④Custom ClassLoader
屬於應用程式根據自身需要自訂的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader
載入過程中會先檢查類是否被已載入,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已載入就視為已載入此類,保證此類只所有ClassLoader載入一次。而載入的順序是自頂向下,也就是由上層來逐層嘗試載入此類。
(3)類執行機制
JVM是基於堆棧的虛擬機器。JVM為每個新建立的線程都分配一個堆棧.也就是說,對於一個Java程式來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位儲存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
JVM執行class位元組碼,線程建立後,都會產生程式計數器(PC)和棧(Stack),程式計數器存放下一條要執行的指令在方法內的位移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變數區和運算元棧兩部分組成,局部變數區用於存放方法中的局部變數和參數,運算元棧中用於存放方法執行過程中產生的中間結果。棧的結構如所示:
3、JVM記憶體管理及記憶體回收機制
JVM記憶體結構分為:方法區(method),棧記憶體(stack),堆記憶體(heap),本地方法棧(java中的jni調用),結構圖如下所示:
(1)堆記憶體(heap)
所有通過new建立的對象的記憶體都在堆中分配,其大小可以通過-Xmx和-Xms來控制。
作業系統有一個記錄空閑記憶體位址的鏈表,當系統收到程式的申請時,會遍曆該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閑結點鏈表中刪除,並將該結點的空間分配給程式,另外,對於大多數系統,會在這塊記憶體空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本記憶體空間。但由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閑鏈表中。這時由new分配的記憶體,一般速度比較慢,而且容易產生記憶體片段,不過用起來最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配記憶體,它不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊記憶體,雖然這種方法用起來最不方便,但是速度快,也是最靈活的。堆記憶體是向高地址擴充的資料結構,是不連續的記憶體地區。由於系統是用鏈表來儲存的空閑記憶體位址的,自然是不連續的,而鏈表的遍曆方向是由低地址向高地址。堆的大小受限於電腦系統中有效虛擬記憶體。由此可見,堆獲得的空間比較靈活,也比較大。
(2)棧記憶體(stack)
在Windows下, 棧是向低地址擴充的資料結構,是一塊連續的記憶體地區。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時間就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。只要棧的剩餘空間大於所申請空間,系統將為程式提供記憶體,否則將報異常提示棧溢出。 由系統自動分配,速度較快。但程式員是無法控制的。
堆記憶體與棧記憶體需要說明:
基礎資料類型直接在棧空間分配,方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。引用資料類型,需要用new來建立,既在棧空間分配一個地址空間,又在堆空間指派至的類變數 。方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完成後從棧空間回收。局部變數new出來時,在棧空間和堆空間中分配空間,當局部變數生命週期結束後,棧空間立刻被回收,堆空間地區等待GC回收。方法調用時傳入的literal參數,先在棧空間分配,在方法調用完成後從棧空間收回。字串常量、static在DATA地區分配,this在堆空間分配。數組既在棧空間分配數組名稱,又在堆空間分配數組實際的大小。
如:
(3)本地方法棧(java中的jni調用)
用於支援native方法的執行,儲存了每個native方法調用的狀態。對於本地方法介面,實現JVM並不要求一定要有它的支援,甚至可以完全沒有。Sun公司實現Java本地介面(JNI)是出於可移植性的考慮,當然我們也可以設計出其它的本地介面來代替Sun公司的JNI。但是這些設計與實現是比較複雜的事情,需要確保記憶體回收行程不會將那些正在被本地方法調用的對象釋放掉。
(4)方法區(method)
它儲存方法代碼(編譯後的java代碼)和符號表。存放了要載入的類資訊、靜態變數、final類型的常量、屬性和方法資訊。JVM用持久代(Permanet Generation)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。
記憶體回收機制
堆裡聚集了所有由應用程式建立的對象,JVM也有對應的指令比如 new, newarray, anewarray和multianewarray,然並沒有向 C++ 的 delete,free 等釋放空間的指令,Java的所有釋放都由 GC 來做,GC除了做回收記憶體之外,另外一個重要的工作就是記憶體的壓縮,這個在其他的語言中也有類似的實現,相比 C++ 不僅好用,而且增加了安全性,當然她也有弊端,比如效能這個大問題。
4、Java虛擬機器的運行過程樣本
上面對虛擬機器的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。
虛擬機器通過調用某個指定類的方法main啟動,傳遞給main一個字串數組參數,使指定的類被裝載,同時連結該類所使用的其它的類型,並且初始化它們。例如對於程式:
編譯後在命令列模式下鍵入: java HelloApp run virtual machine
將通過調用HelloApp的方法main來啟動java虛擬機器,傳遞給main一個包含三個字串"run"、"virtual"、"machine"的數組。現在我們略述虛擬機器在執行HelloApp時可能採取的步驟。
開始試圖執行類HelloApp的main方法,發現該類並沒有被裝載,也就是說虛擬機器當前不包含該類的二進位代表,於是虛擬機器使用ClassLoader試圖尋找這樣的二進位代表。如果這個進程失敗,則拋出一個異常。類被裝載後同時在main方法被調用之前,必須對類HelloApp與其它類型進行連結然後初始化。連結包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則建立類或介面的靜態域以及把這些域初始化為標準的預設值,解析負責檢查主類對其它類或介面的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態初始化函數和靜態域的初始化構造方法的執行。一個類在初始化之前它的父類必須被初始化。整個過程如下:
本文來自:http://blog.csdn.net/CSDN_980979768/article/details/47281037?locationNum=7&fps=1
(轉)Java 詳解 JVM 工作原理和流程