深入理解jvm(转)
(? 首先這里澄清兩個(gè)概念:JVM實(shí)例和JVM執(zhí)行引擎實(shí)例,JVM實(shí)例對(duì)應(yīng)了一個(gè)獨(dú)立運(yùn)行的java程序,而JVM執(zhí)行引擎實(shí)例則對(duì)應(yīng)了屬于用戶(hù)運(yùn)行程序的線(xiàn)程;也就是JVM實(shí)例是進(jìn)程級(jí)別,而執(zhí)行引擎是線(xiàn)程級(jí)別的。)
一、 JVM的生命周期
JVM實(shí)例的誕生:當(dāng)啟動(dòng)一個(gè)Java程序時(shí),一個(gè)JVM實(shí)例就產(chǎn)生了,任何一個(gè)擁有public static void main(String[] args)函數(shù)的class都可以作為JVM實(shí)例運(yùn)行的起點(diǎn),既然如此,那么JVM如何知道是運(yùn)行class A的main而不是運(yùn)行class B的main呢?這就需要顯式的告訴JVM類(lèi)名,也就是我們平時(shí)運(yùn)行java程序命令的由來(lái),如java classA hello world,這里java是告訴os運(yùn)行Sun java 2 SDK的java虛擬機(jī),而classA則指出了運(yùn)行JVM所需要的類(lèi)名。
JVM實(shí)例的運(yùn)行:main()作為該程序初始線(xiàn)程的起點(diǎn),任何其他線(xiàn)程均由該線(xiàn)程啟動(dòng)。JVM內(nèi)部有兩種線(xiàn)程:守護(hù)線(xiàn)程和非守護(hù)線(xiàn)程,main()屬于非守護(hù)線(xiàn)程,守護(hù)線(xiàn)程通常由JVM自己使用,java程序也可以標(biāo)明自己創(chuàng)建的線(xiàn)程是守護(hù)線(xiàn)程。
JVM實(shí)例的消亡:當(dāng)程序中的所有非守護(hù)線(xiàn)程都終止時(shí),JVM才退出;若安全管理器允許,程序也可以使用Runtime類(lèi)或者System.exit()來(lái)退出。
二、JVM的體系結(jié)構(gòu)
? 粗略分來(lái),JVM的內(nèi)部體系結(jié)構(gòu)分為三部分,分別是:類(lèi)裝載器(ClassLoader)子系統(tǒng),運(yùn)行時(shí)數(shù)據(jù)區(qū),和執(zhí)行引擎。
?下面將先介紹類(lèi)裝載器,然后是執(zhí)行引擎,最后是運(yùn)行時(shí)數(shù)據(jù)區(qū)
1,類(lèi)裝載器,顧名思義,就是用來(lái)裝載.class文件的。JVM的兩種類(lèi)裝載器包括:啟動(dòng)類(lèi)裝載器和用戶(hù)自定義類(lèi)裝載器,啟動(dòng)類(lèi)裝載器是JVM實(shí)現(xiàn)的一部分,用戶(hù)自定義類(lèi)裝載器則是Java程序的一部分,必須是ClassLoader類(lèi)的子類(lèi)。(下面所述情況是針對(duì)Sun JDK1.2)
動(dòng)類(lèi)裝載器:只在系統(tǒng)類(lèi)(java API的類(lèi)文件)的安裝路徑查找要裝入的類(lèi)??????
用戶(hù)自定義類(lèi)裝載器:?
系統(tǒng)類(lèi)裝載器:在JVM啟動(dòng)時(shí)創(chuàng)建,用來(lái)在CLASSPATH目錄下查找要裝入的類(lèi)
其他用戶(hù)自定義類(lèi)裝載器:這里有必要先說(shuō)一下ClassLoader類(lèi)的幾個(gè)方法,了解它們對(duì)于了解自定義類(lèi)裝載器如何裝載.class文件至關(guān)重要。
protected final Class defineClass(String name, byte data[], int offset, int length)
protected final Class defineClass(String name, byte data[], int offset, int length, ProtectionDomain protectionDomain);
protected final Class findSystemClass(String name)
protected final void resolveClass(Class c)
defineClass用來(lái)將二進(jìn)制class文件(新類(lèi)型)導(dǎo)入到方法區(qū),也就是這里指的類(lèi)是用戶(hù)自定義的類(lèi)(也就是負(fù)責(zé)裝載類(lèi))
????? findSystemClass通過(guò)類(lèi)型的全限定名,先通過(guò)系統(tǒng)類(lèi)裝載器或者啟動(dòng)類(lèi)裝載器來(lái)裝載,并返回Class對(duì)象。
ResolveClass: 讓類(lèi)裝載器進(jìn)行連接動(dòng)作(包括驗(yàn)證,分配內(nèi)存初始化,將類(lèi)型中的符號(hào)引用解析為直接引用),這里涉及到j(luò)ava命名空間的問(wèn)題,JVM保證被一個(gè)類(lèi)裝載器裝載的類(lèi)所引用的所有類(lèi)都被這個(gè)類(lèi)裝載器裝載,同一個(gè)類(lèi)裝載器裝載的類(lèi)之間可以相互訪(fǎng)問(wèn),但是不同類(lèi)裝載器裝載的類(lèi)看不見(jiàn)對(duì)方,從而實(shí)現(xiàn)了有效的屏蔽。
?2,?執(zhí)行引擎:它或者在執(zhí)行字節(jié)碼,或者執(zhí)行本地方法
?? 要說(shuō)執(zhí)行引擎,就不得不的指令集,每一條指令包含一個(gè)單字節(jié)的操作碼,后面跟0個(gè)或者多個(gè)操作數(shù)。(一)指令集以棧為設(shè)計(jì)中心,而非以寄存器為中心
這種指令集設(shè)計(jì)如何滿(mǎn)足Java體系的要求:
平臺(tái)無(wú)關(guān)性:以棧為中心使得在只有很少register的機(jī)器上實(shí)現(xiàn)java更便利
compiler一般采用stack向連接優(yōu)化器傳遞編譯的中間結(jié)果,若指令集以stack為基礎(chǔ),則有利于運(yùn)行時(shí)進(jìn)行的優(yōu)化工作與執(zhí)行即時(shí)編譯或者自適應(yīng)優(yōu)化的執(zhí)行引擎結(jié)合,通俗的說(shuō)就是使編譯和運(yùn)行用的數(shù)據(jù)結(jié)構(gòu)統(tǒng)一,更有利于優(yōu)化的開(kāi)展。
網(wǎng)絡(luò)移動(dòng)性:class文件的緊湊性。
安全性:指令集中絕大部分操作碼都指明了操作的類(lèi)型。(在裝載的時(shí)候使用數(shù)據(jù)流分析期進(jìn)行一次性驗(yàn)證,而非在執(zhí)行每條指令的時(shí)候進(jìn)行驗(yàn)證,有利于提高執(zhí)行速度)。
(二)執(zhí)行技術(shù)
主要的執(zhí)行技術(shù)有:解釋,即時(shí)編譯,自適應(yīng)優(yōu)化、芯片級(jí)直接執(zhí)行
其中解釋屬于第一代JVM,即時(shí)編譯JIT屬于第二代JVM,自適應(yīng)優(yōu)化(目前Sun的HotspotJVM采用這種技術(shù))則吸取第一代JVM和第二代JVM的經(jīng)驗(yàn),采用兩者結(jié)合的方式
自適應(yīng)優(yōu)化:開(kāi)始對(duì)所有的代碼都采取解釋執(zhí)行的方式,并監(jiān)視代碼執(zhí)行情況,然后對(duì)那些經(jīng)常調(diào)用的方法啟動(dòng)一個(gè)后臺(tái)線(xiàn)程,將其編譯為本地代碼,并進(jìn)行仔細(xì)優(yōu)化。若方法不再頻繁使用,則取消編譯過(guò)的代碼,仍對(duì)其進(jìn)行解釋執(zhí)行。
3,運(yùn)行時(shí)數(shù)據(jù)區(qū):主要包括:方法區(qū),堆,java棧,PC寄存器,本地方法棧
(1)方法區(qū)和堆由所有線(xiàn)程共享
堆:存放所有程序在運(yùn)行時(shí)創(chuàng)建的對(duì)象
方法區(qū):當(dāng)JVM的類(lèi)裝載器加載.class文件,并進(jìn)行解析,把解析的類(lèi)型信息放入方法區(qū)。
(2)Java棧和PC寄存器由線(xiàn)程獨(dú)享,在新線(xiàn)程創(chuàng)建時(shí)間里
(3)本地方法棧: 存儲(chǔ)本地方法調(diào)用的狀態(tài)
上邊總體介紹了運(yùn)行時(shí)數(shù)據(jù)區(qū)的主要內(nèi)容,下邊進(jìn)行詳細(xì)介紹,要介紹數(shù)據(jù)區(qū),就不得不說(shuō)明JVM中的數(shù)據(jù)類(lèi)型。
JVM中的數(shù)據(jù)類(lèi)型:JVM中基本的數(shù)據(jù)單元是word,而word的長(zhǎng)度由JVM具體的實(shí)現(xiàn)者來(lái)決定
數(shù)據(jù)類(lèi)型包括基本類(lèi)型和引用類(lèi)型,
(1)???????????????? 基本類(lèi)型包括:數(shù)值類(lèi)型(包括除boolean外的所有的java基本數(shù)據(jù)類(lèi)型),boolean(在JVM中使用int來(lái)表示,0表示false,其他int值均表示true)和returnAddress(JVM的內(nèi)部類(lèi)型,用來(lái)實(shí)現(xiàn)finally子句)。
(2)???????????????? 引用類(lèi)型包括:數(shù)組類(lèi)型,類(lèi)類(lèi)型,接口類(lèi)型
前邊講述了JVM中數(shù)據(jù)的表示,下面讓我們輸入到JVM的數(shù)據(jù)區(qū)
首先來(lái)看方法區(qū):
上邊已經(jīng)提到,方法區(qū)主要用來(lái)存儲(chǔ)JVM從class文件中提取的類(lèi)型信息,那么類(lèi)型信息是如何存儲(chǔ)的呢?眾所周知,Java使用的是大端序(big—endian:即低字節(jié)的數(shù)據(jù)存儲(chǔ)在高位內(nèi)存上,如對(duì)于1234,12是高位數(shù)據(jù),34為低位數(shù)據(jù),則java中的存儲(chǔ)格式應(yīng)該為12存在內(nèi)存的低地址,34存在內(nèi)存的高地址,x86中的存儲(chǔ)格式與之相反)來(lái)存儲(chǔ)數(shù)據(jù),這實(shí)際上是在class文件中數(shù)據(jù)的存儲(chǔ)格式,但是當(dāng)數(shù)據(jù)倒入到方法區(qū)中時(shí),JVM可以以任何方式來(lái)存儲(chǔ)它。
類(lèi)型信息:包括class的全限定名,class的直接父類(lèi),類(lèi)類(lèi)型還是接口類(lèi)型,類(lèi)的修飾符(public,等),所有直接父接口的列表,Class對(duì)象提供了訪(fǎng)問(wèn)這些信息的窗口(可通過(guò)Class.forName(“”)或instance.getClass()獲得),下面是Class的方法,相信大家看了會(huì)恍然大悟,(原來(lái)如此J)
getName(), getSuperClass(), isInterface(), getInterfaces(), getClassLoader();
static變量作為類(lèi)型信息的一部分保存
指向ClassLoader類(lèi)的引用:在動(dòng)態(tài)連接時(shí)裝載該類(lèi)中引用的其他類(lèi)
指向Class類(lèi)的引用:必然的,上邊已述
該類(lèi)型的常量池:包括直接常量(String,integer和float point常量)以及對(duì)其他類(lèi)型、字段和方法的符號(hào)引用(注意:這里的常量池并不是普通意義上的存儲(chǔ)常量的地方,這些符號(hào)引用可能是我們?cè)诰幊讨兴佑|到的變量),由于這些符號(hào)引用,使得常量池成為java程序動(dòng)態(tài)連接中至關(guān)重要的部分
字段信息:普通意義上的類(lèi)型中聲明的字段
方法信息:類(lèi)型中各個(gè)方法的信息
編譯期常量:指用final聲明或者用編譯時(shí)已知的值初始化的類(lèi)變量
??? class將所有的常量復(fù)制至其常量池或者其字節(jié)碼流中。
方法表:一個(gè)數(shù)組,包括所有它的實(shí)例可能調(diào)用的實(shí)例方法的直接引用(包括從父類(lèi)中繼承來(lái)的)
除此之外,若某個(gè)類(lèi)不是抽象和本地的,還要保存方法的字節(jié)碼,操作數(shù)棧和該方法的棧幀,異常表。
舉例:
class Lava{
? private int speed = 5;
? void flow(){}
}
class Volcano{
? public static void main(String[] args){
Lava lava = new Lava();
lava.flow();
}
}
運(yùn)行命令java Volcano;
(1)???????? JVM找到Volcano.class倒入,并提取相應(yīng)的類(lèi)型信息到方法區(qū)。通過(guò)執(zhí)行方法區(qū)中的字節(jié)碼,JVM執(zhí)行main()方法,(執(zhí)行時(shí)會(huì)一直保存指向Vocano類(lèi)的常量池的指針)
(2)???????? Main()中第一條指令告訴JVM需為列在常量池第一項(xiàng)的類(lèi)分配內(nèi)存(此處再次說(shuō)明了常量池并非只存儲(chǔ)常量信息),然后JVM找到常量池的第一項(xiàng),發(fā)現(xiàn)是對(duì)Lava類(lèi)的符號(hào)引用,則檢查方法區(qū),看Lava類(lèi)是否裝載,結(jié)果是還未裝載,則查找“Lava.class”,將類(lèi)型信息寫(xiě)入方法區(qū),并將方法區(qū)Lava類(lèi)信息的指針來(lái)替換Volcano原常量池中的符號(hào)引用,即用直接引用來(lái)替換符號(hào)引用。
(3)???????? JVM看到new關(guān)鍵字,準(zhǔn)備為L(zhǎng)ava分配內(nèi)存,根據(jù)Volcano的常量池的第一項(xiàng)找到Lava在方法區(qū)的位置,并分析需要多少對(duì)空間,確定后,在堆上分配空間,并將speed變量初始為0,并將lava對(duì)象的引用壓到棧中
(4)???????? 調(diào)用lava的flow()方法
好了,大致了解了方法區(qū)的內(nèi)容后,讓我們來(lái)看看堆
java對(duì)象的堆實(shí)現(xiàn):
java對(duì)象主要由實(shí)例變量(包括自己所屬的類(lèi)和其父類(lèi)聲明的)以及指向方法區(qū)中類(lèi)數(shù)據(jù)的指針,指向方法表的指針,對(duì)象鎖(非必需), 等待集合(非必需),GC相關(guān)的數(shù)據(jù)(非必需)(主要視GC算法而定,如對(duì)于標(biāo)記并清除算法,需要標(biāo)記對(duì)象是否被引用,以及是否已調(diào)用finalize()方法)。
那么為什么java對(duì)象中要有指向類(lèi)數(shù)據(jù)的指針呢?我們從幾個(gè)方面來(lái)考慮
首先:當(dāng)程序中將一個(gè)對(duì)象引用轉(zhuǎn)為另一個(gè)類(lèi)型時(shí),如何檢查轉(zhuǎn)換是否允許?需用到類(lèi)數(shù)據(jù)
其次:動(dòng)態(tài)綁定時(shí),并不是需要引用類(lèi)型,而是需要運(yùn)行時(shí)類(lèi)型,
這里的迷惑是:為什么類(lèi)數(shù)據(jù)中保存的是實(shí)際類(lèi)型,而非引用類(lèi)型?這個(gè)問(wèn)題先留下來(lái),我想在后續(xù)的讀書(shū)筆記中應(yīng)該能明白
指向方法表的指針:這里和C++的VTBL是類(lèi)似的,有利于提高方法調(diào)用的效率
對(duì)象鎖:用來(lái)實(shí)現(xiàn)多個(gè)線(xiàn)程對(duì)共享數(shù)據(jù)的互斥訪(fǎng)問(wèn)
等待集合:用來(lái)讓多個(gè)線(xiàn)程為完成共同目標(biāo)而協(xié)調(diào)功過(guò)。(注意Object類(lèi)中的wait(),notify(),notifyAll()方法)。
Java數(shù)組的堆實(shí)現(xiàn):數(shù)組也擁有一個(gè)和他們的類(lèi)相關(guān)聯(lián)的Class實(shí)例,具有相同dimension和type的數(shù)組是同一個(gè)類(lèi)的實(shí)例。數(shù)組類(lèi)名的表示:如[[Ljava/lang/Object 表示Object[][],[I表示int[],[[[B表示byte[][][]
至此,堆已大致介紹完畢,下面來(lái)介紹程序計(jì)數(shù)器和java棧
程序計(jì)數(shù)器:為每個(gè)線(xiàn)程獨(dú)有,在線(xiàn)程啟動(dòng)時(shí)創(chuàng)建,
? 若thread執(zhí)行java方法,則PC保存下一條執(zhí)行指令的地址。
? 若thread執(zhí)行native方法,則Pc的值為undefined
Java棧:java棧以幀為單位保存線(xiàn)程的運(yùn)行狀態(tài),java棧只有兩種操作,幀的壓棧和出棧。
每個(gè)幀代表一個(gè)方法,java方法有兩種返回方式,return和拋出異常,兩種方式都會(huì)導(dǎo)致該方法對(duì)應(yīng)的幀出棧和釋放內(nèi)存。
幀的組成:局部變量區(qū)(包括方法參數(shù)和局部變量,對(duì)于instance方法,還要首先保存this類(lèi)型,其中方法參數(shù)按照聲明順序嚴(yán)格放置,局部變量可以任意放置),操作數(shù)棧,幀數(shù)據(jù)區(qū)(用來(lái)幫助支持常量池的解析,正常方法返回和異常處理)。
本地方法棧:依賴(lài)于本地方法的實(shí)現(xiàn),如某個(gè)JVM實(shí)現(xiàn)的本地方法借口使用C連接模型,則本地方法棧就是C棧,可以說(shuō)某線(xiàn)程在調(diào)用本地方法時(shí),就進(jìn)入了一個(gè)不受JVM限制的領(lǐng)域,也就是JVM可以利用本地方法來(lái)動(dòng)態(tài)擴(kuò)展本身。
?
好!至此,JVM的大致介紹完畢,其他關(guān)于JVM的內(nèi)部實(shí)現(xiàn)我將陸續(xù)補(bǔ)充,歡迎大家和我探討問(wèn)題!
原文鏈接:http://dev.firnow.com/course/3_program/java/javashl/200726/11497.html
總結(jié)
以上是生活随笔為你收集整理的深入理解jvm(转)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python爬虫之爬取起点中文网小说
- 下一篇: 零基础学习python第二课笔记