一、Java平臺(tái)的結(jié)構(gòu)圖
二、JVM與JRE、JDK關(guān)系?JVM:Java Virtual Machine(Java虛擬機(jī)),負(fù)責(zé)執(zhí)行符合規(guī)范的Class文件 JRE:Java Runtime Environment(java運(yùn)行環(huán)境),包含JVM和類庫(kù) JDK:Java Development Kit(java開發(fā)工具包),包含JRE和開發(fā)工具包,例如javac、javah 相關(guān)文章:http://blog.csdn.net/ljheee/article/details/50810570
三、JVM所處的位置
(1)通常工作中所接觸的基本是Java庫(kù)和應(yīng)用以及Java核心類庫(kù),知道如何使用就可以了,但是歸根結(jié)底代碼都是要編譯成class文件由Java虛擬機(jī)裝載執(zhí)行,所產(chǎn)生的結(jié)果或者現(xiàn)象都可以通過Java虛擬機(jī)的運(yùn)行機(jī)制來(lái)解釋。一些相同的代碼會(huì)由于虛擬機(jī)的實(shí)現(xiàn)不同而產(chǎn)生不同結(jié)果。 (2)在Java平臺(tái)的結(jié)構(gòu)中,可以看出,Java虛擬機(jī)(JVM)處在核心的位置,是程序與底層操作系統(tǒng)和硬件無(wú)關(guān)的關(guān)鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統(tǒng),其中依賴于平臺(tái)的部分稱為適配器;JVM通過移植接口在具體的平臺(tái)和操作系統(tǒng)上實(shí)現(xiàn);在JVM的上方是Java的基本類庫(kù)和擴(kuò)展類庫(kù)以及它們的API, 利用Java API編寫的應(yīng)用程序(application)和小程序(Java applet)可以在任何Java平臺(tái)上運(yùn)行而無(wú)需考慮底層平臺(tái),就是因?yàn)橛蠮ava虛擬機(jī)(JVM)實(shí)現(xiàn)了程序與操作系統(tǒng)的分離,從而實(shí)現(xiàn)了Java的平臺(tái)無(wú)關(guān)性。 (3)對(duì)JVM規(guī)范的的抽象說(shuō)明是一些概念的集合,它們已經(jīng)在書《The Java Virtual Machine Specification》(《Java虛擬機(jī)規(guī)范》)中被詳細(xì)地描述了;對(duì)JVM的具體實(shí)現(xiàn)要么是軟件,要么是軟件和硬件的組合,它已經(jīng)被許多生產(chǎn)廠商所實(shí)現(xiàn),并存在于多種平臺(tái)之上;運(yùn)行Java程序的任務(wù)由JVM的運(yùn)行期實(shí)例單個(gè)承擔(dān)。 (4)JVM可以由不同的廠商來(lái)實(shí)現(xiàn)。由于廠商的不同必然導(dǎo)致JVM在實(shí)現(xiàn)上的一些不同,像國(guó)內(nèi)就有著名的TaobaoVM;然而JVM還是可以實(shí)現(xiàn)跨平臺(tái)的特性,這就要?dú)w功于設(shè)計(jì)JVM時(shí)的體系結(jié)構(gòu)了。 (5)JVM在它的生存周期中有一個(gè)明確的任務(wù),那就是裝載字節(jié)碼文件,一旦字節(jié)碼進(jìn)入虛擬機(jī),它就會(huì)被解釋器解釋執(zhí)行,或者是被即時(shí)代碼發(fā)生器有選擇的轉(zhuǎn)換成機(jī)器碼執(zhí)行,即Java程序被執(zhí)行。因此當(dāng)Java程序啟動(dòng)的時(shí)候,就產(chǎn)生JVM的一個(gè)實(shí)例;當(dāng)程序運(yùn)行結(jié)束的時(shí)候,該實(shí)例也跟著消失了。 四、Class字節(jié)碼編譯后被Java虛擬機(jī)所執(zhí)行的代碼使用了一種平臺(tái)中立(不依賴于特定硬件及操作系統(tǒng)的)的二進(jìn)制格式來(lái)表示,并且經(jīng)常(但并非絕對(duì))以文件的形式存儲(chǔ),因此這種格式被稱為Class文件格式。Class文件格式中精確地定義了類與接口的表示形式,包括在平臺(tái)相關(guān)的目標(biāo)文件格式中一些細(xì)節(jié)上的慣例, 正如概念所說(shuō),Java為了能夠?qū)崿F(xiàn)平臺(tái)無(wú)關(guān)性,制定了一套自己的二進(jìn)制格式,并經(jīng)常以文件的方式存儲(chǔ),稱為Class文件。這樣在不同平臺(tái)上,只要都安裝了Java虛擬機(jī),具備Java運(yùn)行環(huán)境[JRE],那么都可以運(yùn)行相同的Class文件。 上圖描述了Java程序運(yùn)行的一個(gè)全過程,也可以看出Java平臺(tái)由Java虛擬機(jī)和Java應(yīng)用程序接口搭建,Java語(yǔ)言則是進(jìn)入這個(gè)平臺(tái)的通道,用Java語(yǔ)言編寫并編譯的程序可以運(yùn)行在這個(gè)平臺(tái)上。
由Java源文件編譯生成字節(jié)碼文件,這個(gè)過程非常復(fù)雜,學(xué)過《編譯原理》的朋友都知道必須經(jīng)過詞法分析、語(yǔ)法分析、語(yǔ)義分析、中間代碼生成、代碼優(yōu)化等;同樣的,Java源文件到字節(jié)碼的生成也想要經(jīng)歷這些步驟。Javac編譯器的最后任務(wù)就是調(diào)用con.sun.tools.javac.jvm.Gen類將這課語(yǔ)法樹編譯為Java字節(jié)碼文件。 其實(shí),所謂的編譯字節(jié)碼,無(wú)非就是將符合Java語(yǔ)法規(guī)范的Java代碼轉(zhuǎn)化為符合JVM規(guī)范的字節(jié)碼文件。JVM的架構(gòu)模型是基于棧的,大部分都需要通過棧來(lái)完成。 字節(jié)碼結(jié)構(gòu)比較特殊,其內(nèi)部不包含任何的分隔符,無(wú)法人工區(qū)分段落(字節(jié)碼文件本身就是給機(jī)器讀的),所以無(wú)論是字節(jié)順序、數(shù)量都是有嚴(yán)格規(guī)定的,所有16位、32位、64位長(zhǎng)度的數(shù)據(jù)都將構(gòu)造成2個(gè)、4個(gè)、8個(gè)-----8位字節(jié)單位來(lái)表示,多字節(jié)數(shù)據(jù)項(xiàng)總是按照Big-endian順序(高位字節(jié)在地址的最低位,地位字節(jié)在地址的最高位)來(lái)進(jìn)行存儲(chǔ)。 參考《Java虛擬機(jī)規(guī)范 Java SE7版》的描述,每一個(gè)字節(jié)碼其實(shí)都對(duì)應(yīng)著全局唯一的一個(gè)類或者接口的定義信息。字節(jié)碼文件才用的是一種類似于C語(yǔ)言結(jié)構(gòu)體的偽結(jié)構(gòu)來(lái)描述字節(jié)碼文件格式。字節(jié)碼文件中對(duì)應(yīng)的“基本類型”u1,u2,u4,u8分別表示無(wú)符號(hào)1、2、4、8個(gè)字節(jié)。 Class文件----總體格式 值得一提的是,一個(gè)有效的class字節(jié)碼文件的前4個(gè)字節(jié)為0xCAFEBABE,都是固定的,被稱為“魔術(shù)”,即magic。它就是JVM用于校驗(yàn)所讀取的目標(biāo)文件是否是一個(gè)有效且合法的字節(jié)碼文件。由此可見,JVM并不是通過判斷文件后綴名的方式來(lái)校驗(yàn),以防止人為手動(dòng)修改。 五、Java虛擬機(jī)的體系結(jié)構(gòu)一個(gè)JVM實(shí)例的行為不光是它自己的事,還涉及到它的子系統(tǒng)、存儲(chǔ)區(qū)域、數(shù)據(jù)類型和指令這些部分,它們描述了JVM的一個(gè)抽象的內(nèi)部體系結(jié)構(gòu),其目的不光規(guī)定實(shí)現(xiàn)JVM時(shí)它內(nèi)部的體系結(jié)構(gòu),更重要的是提供了一種方式,用于嚴(yán)格定義實(shí)現(xiàn)時(shí)的外部行為。每個(gè)JVM都有兩種機(jī)制,一個(gè)是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統(tǒng);另外的一個(gè)負(fù)責(zé)執(zhí)行包含在已裝載的類或接口中的指令,叫做運(yùn)行引擎。每個(gè)JVM又包括方法區(qū)、堆、Java棧、程序計(jì)數(shù)器和本地方法棧這五個(gè)部分,這幾個(gè)部分和類裝載機(jī)制與運(yùn)行引擎機(jī)制一起組成的體系結(jié)構(gòu)圖為:
Java虛擬機(jī)定義了若干種程序運(yùn)行期間會(huì)使用到的運(yùn)行時(shí)數(shù)據(jù)區(qū),其中有一些會(huì)隨著虛擬機(jī)啟動(dòng)而創(chuàng)建,隨著虛擬機(jī)退出而銷毀。另外一些則是與線程一一對(duì)應(yīng)的,這些與線程對(duì)應(yīng)的數(shù)據(jù)區(qū)域會(huì)隨著線程開始和結(jié)束而創(chuàng)建和銷毀。 可以看出Java虛擬機(jī)的運(yùn)行時(shí)數(shù)據(jù)區(qū)包括了:方法區(qū)、Java堆、Java虛擬機(jī)棧、PC寄存器、本地方法棧,還有常量池。它們被分為兩大類-----線程共享、私有數(shù)據(jù)區(qū)。 1.線程共享數(shù)據(jù)區(qū)包括:Java堆、方法區(qū)、常量池。它們會(huì)隨著虛擬機(jī)啟動(dòng)而創(chuàng)建,隨著虛擬機(jī)退出而銷毀。 (1)Java堆推薦文章:http://blog.csdn.net/ljheee/article/details/52196455 Java堆在虛擬機(jī)啟動(dòng)的時(shí)候被創(chuàng)建,Java堆主要用來(lái)為類實(shí)例對(duì)象和數(shù)組分配內(nèi)存。Java虛擬機(jī)規(guī)范并沒有規(guī)定對(duì)象在堆中的形式。 在Java中,堆被劃分成兩個(gè)不同的區(qū)域:新生代( Young )、老年代( Old );這也就是JVM采用的“分代收集算法”,簡(jiǎn)單說(shuō),就是針對(duì)不同特征的java對(duì)象采用不同的策略實(shí)施存放和回收,自然所用分配機(jī)制和回收算法就不一樣。新生代( Young ) 又被劃分為三個(gè)區(qū)域:Eden、From Survivor、To Survivor。(《Java虛擬機(jī)精講》(高翔龍...))分代收集算法:采用不同算法處理[存放和回收]Java瞬時(shí)對(duì)象和長(zhǎng)久對(duì)象。大部分Java對(duì)象都是瞬時(shí)對(duì)象,朝生夕滅,存活很短暫,通常存放在Young新生代,采用復(fù)制算法對(duì)新生代進(jìn)行垃圾回收。老年代對(duì)象的生命周期一般都比較長(zhǎng),極端情況下會(huì)和JVM生命周期保持一致;通常采用標(biāo)記-壓縮算法對(duì)老年代進(jìn)行垃圾回收。 這樣劃分的目的是為了使JVM能夠更好的管理堆內(nèi)存中的對(duì)象,包括內(nèi)存的分配以及回收。Java堆可能發(fā)生如下異常情況:如果實(shí)際所需的堆超過了自動(dòng)內(nèi)存管理系統(tǒng)能提供的最大容量,那Java虛擬機(jī)將會(huì)拋出一個(gè)OutOfMemoryError異常。(2)方法區(qū)方法區(qū)在虛擬機(jī)啟動(dòng)的時(shí)候被創(chuàng)建,它存儲(chǔ)了每一個(gè)類的結(jié)構(gòu)信息,例如運(yùn)行時(shí)常量池、字段和方法數(shù)據(jù)、構(gòu)造函數(shù)和普通方法的字節(jié)碼內(nèi)容、還包括在類、實(shí)例、接口初始化時(shí)用到的特殊方法。 方法區(qū)可能發(fā)生如下異常情況: 如果方法區(qū)的內(nèi)存空間不能滿足內(nèi)存分配請(qǐng)求,那Java虛擬機(jī)將拋出一個(gè)OutOfMemoryError異常. (3)常量池運(yùn)行時(shí)常量池(Runtime Constant Pool)是每一個(gè)類或接口的常量池的運(yùn)行時(shí)表示形式,它包括了若干種不同的常量:從編譯期可知的數(shù)值字面量到必須運(yùn)行期解析后才能獲得的方法或字段引用。運(yùn)行時(shí)常量池在方法區(qū)中。 在創(chuàng)建類和接口的運(yùn)行時(shí)常量池時(shí),可能會(huì)發(fā)生如下異常情況:當(dāng)創(chuàng)建類或接口的時(shí)候,如果構(gòu)造運(yùn)行時(shí)常量池所需要的內(nèi)存空間超過了方法區(qū)所能提供的最大值,那Java虛擬機(jī)將會(huì)拋出一個(gè)OutOfMemoryError異常。 2.線程私有數(shù)據(jù)區(qū)包括:PC寄存器、JVM棧、本地方法區(qū)。它們是與線程一一對(duì)應(yīng)的,這些與線程對(duì)應(yīng)的數(shù)據(jù)區(qū)域會(huì)隨著線程開始和結(jié)束而創(chuàng)建和銷毀。 (1)PC寄存器每個(gè)Java虛擬機(jī)線程都有自己的PC寄存器。在某個(gè)線程被新建時(shí),會(huì)獲得一個(gè)PC寄存器。線程當(dāng)前執(zhí)行的方法稱為當(dāng)前方法,PC寄存器用來(lái)存放當(dāng)前方法中當(dāng)前執(zhí)行的字節(jié)碼指令的地址;之所以為每一個(gè)線程都分配一個(gè)PC寄存器,試想:多線程運(yùn)行時(shí),某個(gè)時(shí)間片內(nèi)只執(zhí)行一個(gè)線程,CPU在不停的切換多個(gè)線程,那如何記錄具體每一個(gè)線程上一次執(zhí)行到哪個(gè)位置了呢,這時(shí)候PC寄存器用來(lái)存放當(dāng)前方法中當(dāng)前執(zhí)行的字節(jié)碼指令的地址,就完美解決了,這就是為什么PC寄存器是線程私有數(shù)據(jù)區(qū)的原因。 如果當(dāng)前方法是本地方法(Native),那么寄存器存放undefined。寄存器的大小至少應(yīng)該能夠存放一個(gè)returnAddress類型的數(shù)據(jù)或者與平臺(tái)相關(guān)的本地指針的值。 PC寄存器是惟一一個(gè)沒有明確規(guī)定需要拋出OutOfMemoryError異常的運(yùn)行時(shí)數(shù)據(jù)區(qū)。 (2)JVM棧每個(gè)Java虛擬機(jī)線程都有自己的Java虛擬機(jī)棧。Java虛擬機(jī)棧用來(lái)存放棧幀,而棧幀主要包括了:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接。Java虛擬機(jī)棧允許被實(shí)現(xiàn)為固定大小或者可動(dòng)態(tài)擴(kuò)展的內(nèi)存大小。 Java虛擬機(jī)使用局部變量表來(lái)完成方法調(diào)用時(shí)的參數(shù)傳遞。局部變量表的長(zhǎng)度在編譯期已經(jīng)決定了并存儲(chǔ)于類和接口的二進(jìn)制表示中,一個(gè)局部變量可以保存一個(gè)類型為boolean、byte、char、short、float、reference和returnAddress的數(shù)據(jù),兩個(gè)局部變量可以保存一個(gè)類型為long和double的數(shù)據(jù)。 Java虛擬機(jī)提供一些字節(jié)碼指令來(lái)從局部變量表或者對(duì)象實(shí)例的字段中復(fù)制常量或變量值到操作數(shù)棧中,也提供了一些指令用于從操作數(shù)棧取走數(shù)據(jù)、操作數(shù)據(jù)和把操作結(jié)果重新入棧。在方法調(diào)用的時(shí)候,操作數(shù)棧也用來(lái)準(zhǔn)備調(diào)用方法的參數(shù)以及接收方法返回結(jié)果。 每個(gè)棧幀中都包含一個(gè)指向運(yùn)行時(shí)常量區(qū)的引用支持當(dāng)前方法的動(dòng)態(tài)鏈接。在Class文件中,方法調(diào)用和訪問成員變量都是通過符號(hào)引用來(lái)表示的,動(dòng)態(tài)鏈接的作用就是將符號(hào)引用轉(zhuǎn)化為實(shí)際方法的直接引用或者訪問變量的運(yùn)行是內(nèi)存位置的正確偏移量。 總的來(lái)說(shuō),Java虛擬機(jī)棧是用來(lái)存放局部變量和過程結(jié)果的地方。 Java虛擬機(jī)棧可能發(fā)生如下異常情況: 如果Java虛擬機(jī)棧被實(shí)現(xiàn)為固定大小內(nèi)存,線程請(qǐng)求分配的棧容量超過Java虛擬機(jī)棧允許的最大容量時(shí),Java虛擬機(jī)將會(huì)拋出一個(gè)StackOverflowError異常。 如果Java虛擬機(jī)棧被實(shí)現(xiàn)為動(dòng)態(tài)擴(kuò)展內(nèi)存大小,并且擴(kuò)展的動(dòng)作已經(jīng)嘗試過,但是目前無(wú)法申請(qǐng)到足夠的內(nèi)存去完成擴(kuò)展,或者在建立新的線程時(shí)沒有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,那Java虛擬機(jī)將會(huì)拋出一個(gè)OutOfMemoryError異常。 (3)本地方法區(qū)本地方法棧用于支持native方法的運(yùn)行。(native方法,比如用C/C++實(shí)現(xiàn)的代碼) |
|
來(lái)自: Levy_X > 《JAVAWEB學(xué)習(xí)資料》