【深入理解JVM】JVM概述
1、什么是JVM
JVM全稱是Java Virtual Machine(java虛擬機)。它之所以被稱之為是“虛擬”的,就是因為它僅僅是由一個規范來定義的抽象計算機。我們平時經常使用的Sun HotSpot虛擬機只是其中一個具體的實現(另外還有BEA JRockit、IBM J9等等虛擬機)。
JVM的設計目標是提供一個基于抽象規格描述的計算機模型,為解釋程序開發人員提供很好的靈活性,同時也確保Java代碼可在符合該規范的任何系統上運行。JVM對其實現的某些方面給出了具體的定義,特別是對Java可執行代碼,即字節碼(Bytecode)的格式給出了明確的規格。這一規格包括操作碼和操作數的語法和數值、標識符的數值表示方式、以及Java類文件中的Java對象、常量緩沖池在JVM的存儲映象。這些定義為JVM解釋器開發人員提供了所需的信息和開發環境。Java的設計者希望給開發人員以隨心所欲使用Java的自由。
JVM是java的核心和基礎,在java編譯器和os平臺之間的虛擬處理器。它是一種基于下層的操作系統和硬件平臺并利用軟件方法來實現的抽象的計算機,可以在上面執行java的字節碼程序。
2、 JRE/JDK/JVM
JDK(Java Development Kit)是針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。Java Runtime Environment(JRE)是運行JAVA程序所必須的環境的集合,包含JVM標準實現及Java核心類庫。JVM是Java Virtual Machine(Java虛擬機)的縮寫,是整個java實現跨平臺的最核心的部分,能夠運行以Java語言寫作的軟件程序。
JRE(JavaRuntimeEnvironment,Java運行環境),也就是Java平臺。是運行基于Java語言編寫的程序所不可缺少的運行環境。也是通過它,Java的開發者才得以將自己開發的程序發布到用戶手中,讓用戶使用。所有的Java 程序都要在JRE下才能運行。普通用戶只需要運行已開發好的java程序,安裝JRE即可。JRE中包含了Java virtual machine(JVM),runtime class libraries和Java application launcher,這些是運行Java程序的必要組件。與大家熟知的JDK不同,JRE是Java運行環境,并不是一個開發環境,所以沒有包含任何開發工具(如編譯器和調試器),只是針對于使用Java程序的用戶。
JDK(Java Development Kit)是程序開發者用來來編譯、調試java程序用的開發工具包。JDK是整個JAVA的核心,包括了Java運行環境JRE(Java Runtime Envirnment)、一堆Java工具(javac/java/jdb等)和Java基礎的類庫(即Java API 包括rt.jar)。JDK中包含JRE,在JDK的安裝目錄下有一個名為jre的目錄,里面有兩個文件夾bin和lib,在這里可以認為bin里的就是jvm,lib中則是jvm工作所需要的類庫,而jvm和 lib和起來就稱為jre。JDK的工具也是Java程序,也需要JRE才能運行。為了保持JDK的獨立性和完整性,在JDK的安裝過程中,JRE也是安裝的一部分。所以,在JDK的安裝目錄下有一個名為jre的目錄,用于存放JRE文件。
JVM(JavaVirtualMachine,Java虛擬機)是JRE的一部分。它是通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。就是我們常說的java虛擬機,它是整個java實現跨平臺的最核心的部分,所有的java程序會首先被編譯為.class的類文件,這種類文件可以在虛擬機上執行。也就是說class并不直接與機器的操作系統相對應,而是經過虛擬機間接與操作系統交互,由虛擬機將程序解釋給本地系統執行。只有JVM還不能成class的執行,因為在解釋class的時候JVM需要調用解釋所需要的類庫lib,而jre包含lib類庫。JVM屏蔽了與具體操作系統平臺相關的信息,使得Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。也就是Java語言最重要的特點—跨平臺運行。使用JVM就是為了支持與操作系統無關,實現跨平臺。
總結一句:金字塔結構:JDK=JRE+JVM+其它,運行Java程序一般都要求用戶的電腦安裝JRE環境(Java Runtime Environment);沒有jre,java程序無法運行;而沒有java程序,jre就沒有用武之地。
3、JVM的生命周期
Java的執行過程也就是JVM從啟動到退出的過程。JVM的運行是一個進程單元,可以用jps工具列舉出正在運行的JVM 進程。在一個JVM進程中可以運行多個線程。
當啟動一個Java程序時,一個虛擬機實例也就誕生了。當該程序關閉退出,這個虛擬機實例也就隨之消亡。如果在同一臺計算機上同時運行多個Java程序,將得到多個Java虛擬機實例。每個Java程序都運行于它自己的Java虛擬機實例中。
JVM實例對應了一個獨立運行的java程序,它是進程級別。
3.1、啟動
啟動一個Java程序時,一個JVM實例就產生了,任何一個擁有public static void main(String[] args)函數的class都可以作為JVM實例運行的起點。
3.2、運行
main()作為該程序初始線程的起點,任何其他線程均由該線程啟動。JVM內部有兩種線程:守護線程和非守護線程,main()屬于非守護線程,守護線程通常由JVM自己使用,java程序也可以標明自己創建的線程是守護線程。
3.3、消亡
當程序中的所有非守護線程都終止時,JVM才退出;若安全管理器允許,程序也可以使用Runtime類或者System.exit()來退出。
4、 JVM運行原理
操作系統裝入JVM是通過jdk中Java.exe來完成,通過下面4步來完成JVM環境。
4.1、JVM裝入環境。
JVM提供的方式是操作系統的動態連接文件。既然是文件那就存在一個裝入路徑的問題,Java是怎么找這個路徑的呢?下面基于Windows的實現的分析。
首先查找jre路徑,Java是通過GetApplicationHomeapi來獲得當前的Java.exe絕對路徑,c:\jdk1.7.0_45\bin\Java.exe,然后截取到絕對路徑c:\jdk1.7.0_45\,判斷c:\jdk1.7.0_45\bin\Java.dll文件是否存在,如果存在就把c:\jdk1.7.0_45\作為jre路徑,如果不存在則判斷c:\jdk1.7.0_45\jre\bin\Java.dll是否存在,如果存在這c:\jdk1.7.0_45\jre作為jre路徑,如果不存在調用GetPublicJREHome查HKEY_LOCAL_MACHINE\Software\JavaSoft\JavaRuntime Environment\“當前JRE版本號”\JavaHome的路徑為jre路徑。
然后裝載JVM.cfg文件。在我們的jdk目錄中jre\bin\server和jre\bin\client都有JVM.dll文件存在,而Java正是通過JVM.cfg配置文件來管理這些不同版本的JVM.dll的。
最后獲得JVM.dll的路徑,JRE路徑+\bin+\JVM類型字符串+\JVM.dll就是JVM的文件路徑了,但是如果在調用Java程序時用-XXaltJVM=參數指定的路徑path,就直接用path+\JVM.dll文件做為JVM.dll的文件路徑。
4.2、裝載JVM.dll
通過第一步已經找到了JVM的路徑,Java通過LoadJavaVM來裝入JVM.dll文件。裝入工作很簡單,就是調用Windows API函數:LoadLibrary裝載JVM.dll動態連接庫.然后把JVM.dll中的導出函數JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs掛接到InvocationFunctions變量的CreateJavaVM和GetDefaultJavaVMInitArgs函數指針變量上。JVM.dll的裝載工作宣告完成。
4.3、初始化JVM。
掛接到JNIENV(JNI調用接口)實例,獲得本地調用接口,這樣就可以在Java中調用JVM的函數了。調用InvocationFunctions->CreateJavaVM也就是JVM中JNI_CreateJavaVM方法獲得JNIEnv結構的實例。
4.4、運行Java程序.
Java程序有兩種方式一種是jar包,一種是class。運行jar(Java -jarXXX.jar)的時候,Java.exe調用GetMainClassName函數,該函數先獲得JNIEnv實例然后調用Java類Java.util.jar.JarFileJNIEnv中方法getManifest()并從返回的Manifest對象中取getAttributes(“Main-Class”)的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主類名作為運行的主類。之后main函數會調用Java.c中LoadClass方法裝載該主類(使用JNIEnv實例的FindClass)。main函數直接調用Java.c中LoadClass方法裝載該類。如果是執行class方法。main函數直接調用Java.c中LoadClass方法裝載該類。
然后main函數調用JNIEnv實例的GetStaticMethodID方法查找裝載的class主類中“publicstatic void main(String[] args)”方法,并判斷該方法是否為public方法,然后調用JNIEnv實例的CallStaticVoidMethod方法調用該Java類的main方法。
5、JVM的體系結構
JVM的基本組成
(1)指令集:JVM指令集 (2)類加載器:在jvm啟動時或者類在運行時將需要的class加載到JVM中 (3)執行引擎:負責執行class文件中的字節碼指令,相當于CPU (4)運行時數據區:將內存劃分成若干個區,分別完成不同的任務 (5)本地方法區:調用C或C++實現的本地方法代碼返回的結果其中主要包括兩個子系統和兩個組件: Classloader(類裝載器) 子系統,Execution engine(執行引擎) 子系統;Runtime data area (運行時數據區域)組件, Native interface(本地接口)組件。
5.1、Class loader子系統:
根據給定的全限定名類名(如java.lang.Object)來裝載class文件的內容到 Runtime data area中的method area(方法區域)。每一個被JVM裝載的類型都有一個與之對應的Java.lang.Class類的實例來表示該類型。該實例可以唯一表示被jvm裝載的class類,這個實例和其他類的實例一樣放在堆內存中。
5.2、Execution engine子系統:
相當于線程,是JVM的核心。執行引擎的作用就是解析JVM字節碼指令,執行classes中的指令,得到結果。
執行引擎也就是執行一條條代碼的一個流程,代碼都包含在方法體中,執行引擎本質上就是執行一個個方法串起來的流程,對應于操作系統的一個線程,每個java線程就是一個執行引擎的實例。
方法的字節碼是由Java虛擬機的指令序列構成的。每一條指令包含一個單字節的操作碼,后面跟隨0個或多個操作數。執行引擎執行字節碼時,首先取得一個操作碼,如果操作碼有操作數,取得它的操作數。它執行操作碼和跟隨的操作數規定的動作,然后再取得下一個操作碼。這個執行字節碼的過程在線程完成前將一直持續。執行引擎由各個廠家實現。SUN的hotspot是一種基于棧的執行引擎。而Android的Dalvik是基于寄存器的執行引擎。任何JVM實現的核心是Execution engine,換句話說:Sun 的JDK 和IBM的JDK好壞主要取決于他們各自實現的Execution engine的好壞。
5.3、Runtime data area 組件:運行時數據區
Java虛擬機定義了若干種程序運行期間會使用到的運行時數據區,其中有一些會隨著虛擬機啟動而創建,隨著虛擬機退出而銷毀。另外一些則是與線程一一對應的,這些與線程對應的數據區域會隨著線程開始和結束而創建和銷毀。
可以看出Java虛擬機的運行時數據區包括了:方法區、Java堆、Java虛擬機棧、PC寄存器、本地方法棧,還有常量池。它們被分為兩大類—–線程共享、私有數據區。
5.3.1、線程共享數據區
包括:Java堆、方法區、常量池。它們會隨著虛擬機啟動而創建,隨著虛擬機退出而銷毀。(1)Java堆
推薦文章:http://blog.csdn.NET/ljheee/article/details/52196455
Java堆在虛擬機啟動的時候被創建,Java堆主要用來為類實例對象和數組分配內存。Java虛擬機規范并沒有規定對象在堆中的形式。
在Java中,堆被劃分成兩個不同的區域:新生代( Young )、老年代( Old );這也就是JVM采用的“分代收集算法”,簡單說,就是針對不同特征的java對象采用不同的策略實施存放和回收,自然所用分配機制和回收算法就不一樣。新生代( Young ) 又被劃分為三個區域:Eden、From Survivor、To Survivor。(《Java虛擬機精講》(高翔龍…))
分代收集算法:采用不同算法處理[存放和回收]Java瞬時對象和長久對象。大部分Java對象都是瞬時對象,朝生夕滅,存活很短暫,通常存放在Young新生代,采用復制算法對新生代進行垃圾回收。老年代對象的生命周期一般都比較長,極端情況下會和JVM生命周期保持一致;通常采用標記-壓縮算法對老年代進行垃圾回收。
這樣劃分的目的是為了使JVM能夠更好的管理堆內存中的對象,包括內存的分配以及回收。
Java堆可能發生如下異常情況:如果實際所需的堆超過了自動內存管理系統能提供的最大容量,那Java虛擬機將會拋出一個OutOfMemoryError異常。
(2)方法區
方法區在虛擬機啟動的時候被創建,它存儲了每一個類的結構信息,例如運行時常量池、字段和方法數據、構造函數和普通方法的字節碼內容、還包括在類、實例、接口初始化時用到的特殊方法。
方法區可能發生如下異常情況: 如果方法區的內存空間不能滿足內存分配請求,那Java虛擬機將拋出一個OutOfMemoryError異常.
(3)常量池
運行時常量池(Runtime Constant Pool)是每一個類或接口的常量池的運行時表示形式,它包括了若干種不同的常量:從編譯期可知的數值字面量到必須運行期解析后才能獲得的方法或字段引用。運行時常量池在方法區中。
在創建類和接口的運行時常量池時,可能會發生如下異常情況:當創建類或接口的時候,如果構造運行時常量池所需要的內存空間超過了方法區所能提供的最大值,那Java虛擬機將會拋出一個OutOfMemoryError異常。
5.3.2、線程私有數據區
包括:PC寄存器、JVM棧、本地方法區。它們是與線程一一對應的,這些與線程對應的數據區域會隨著線程開始和結束而創建和銷毀。
(1)PC寄存器
每個Java虛擬機線程都有自己的PC寄存器。在某個線程被新建時,會獲得一個PC寄存器。線程當前執行的方法稱為當前方法,PC寄存器用來存放當前方法中當前執行的字節碼指令的地址;之所以為每一個線程都分配一個PC寄存器,試想:多線程運行時,某個時間片內只執行一個線程,CPU在不停的切換多個線程,那如何記錄具體每一個線程上一次執行到哪個位置了呢,這時候PC寄存器用來存放當前方法中當前執行的字節碼指令的地址,就完美解決了,這就是為什么PC寄存器是線程私有數據區的原因。
如果當前方法是本地方法(Native),那么寄存器存放undefined。寄存器的大小至少應該能夠存放一個returnAddress類型的數據或者與平臺相關的本地指針的值。
PC寄存器是惟一一個沒有明確規定需要拋出OutOfMemoryError異常的運行時數據區。
(2)JVM棧
每個Java虛擬機線程都有自己的Java虛擬機棧。Java虛擬機棧用來存放棧幀,而棧幀主要包括了:局部變量表、操作數棧、動態鏈接。Java虛擬機棧允許被實現為固定大小或者可動態擴展的內存大小。
Java虛擬機使用局部變量表來完成方法調用時的參數傳遞。局部變量表的長度在編譯期已經決定了并存儲于類和接口的二進制表示中,一個局部變量可以保存一個類型為boolean、byte、char、short、float、reference和returnAddress的數據,兩個局部變量可以保存一個類型為long和double的數據。
Java虛擬機提供一些字節碼指令來從局部變量表或者對象實例的字段中復制常量或變量值到操作數棧中,也提供了一些指令用于從操作數棧取走數據、操作數據和把操作結果重新入棧。在方法調用的時候,操作數棧也用來準備調用方法的參數以及接收方法返回結果。
每個棧幀中都包含一個指向運行時常量區的引用支持當前方法的動態鏈接。在Class文件中,方法調用和訪問成員變量都是通過符號引用來表示的,動態鏈接的作用就是將符號引用轉化為實際方法的直接引用或者訪問變量的運行是內存位置的正確偏移量。
總的來說,Java虛擬機棧是用來存放局部變量和過程結果的地方。
Java虛擬機棧可能發生如下異常情況: 如果Java虛擬機棧被實現為固定大小內存,線程請求分配的棧容量超過Java虛擬機棧允許的最大容量時,Java虛擬機將會拋出一個StackOverflowError異常。
如果Java虛擬機棧被實現為動態擴展內存大小,并且擴展的動作已經嘗試過,但是目前無法申請到足夠的內存去完成擴展,或者在建立新的線程時沒有足夠的內存去創建對應的虛擬機棧,那Java虛擬機將會拋出一個OutOfMemoryError異常。
(3)本地方法區
本地方法棧用于支持native方法的運行。(native方法,比如用C/C++實現的代碼)
總結
以上是生活随笔為你收集整理的【深入理解JVM】JVM概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Spring MVC学习】spring
- 下一篇: 【深入理解JVM】JVM字节码指令集