一、JVM的生命周期: 1)程序開始執(zhí)行,他就運(yùn)行,程序停止,它就結(jié)束。有幾個(gè)程序在執(zhí)行,就有幾個(gè)虛擬機(jī)在工作。只要Java虛擬機(jī)中還有普通的線程在執(zhí)行,Java虛擬機(jī)就不會停止。 2)Java虛擬機(jī)總是開始于一個(gè)main方法,這個(gè)方法必須是公有、返回void、接受一個(gè)字符串?dāng)?shù)組。在程序執(zhí)行時(shí),你必須給Java虛擬機(jī)指明這個(gè)包含main方法的類名。 Main方法是程序的起點(diǎn),他被執(zhí)行的線程初始化為程序的初始線程。程序中其他的線程都由他來啟動(dòng)。 3)Java中的線程分為兩種:守護(hù)線程 (daemon)和普通線程(non-daemon)。守護(hù)線程是Java虛擬機(jī)自己使用的線程,比如負(fù)責(zé)垃圾收集的線程就是一個(gè)守護(hù)線程。包含Main方法的初始線程不是守護(hù)線程。 4)在Java虛擬機(jī)的規(guī)范中定義了一系列的子系統(tǒng)、內(nèi)存區(qū)域、數(shù)據(jù)類型和使用指南。這些組件構(gòu)成了Java虛擬機(jī)的內(nèi)部結(jié)構(gòu),他們不僅僅為Java虛擬機(jī)的實(shí)現(xiàn)提供了清晰的內(nèi)部結(jié)構(gòu),更是嚴(yán)格規(guī)定了Java虛擬機(jī)實(shí)現(xiàn)的外部行為。 二、java虛擬機(jī)的體系結(jié)構(gòu): 每一個(gè)Java虛擬機(jī)都由一個(gè)類加載器子系統(tǒng)負(fù)責(zé)加載程序中的類型(類和接口),并賦予唯一的名字。每一個(gè)Java虛擬機(jī)都有一個(gè)執(zhí)行引擎(execution engine)負(fù)責(zé)執(zhí)行被加載類中包含的指令。 程序的執(zhí)行需要一定的內(nèi)存空間,如字節(jié)碼、被加載類的其他額外信息、程序中的對象、方法的參數(shù)、返回值、本地變量、處理的中間變量等等。Java虛擬機(jī)將這些信息統(tǒng)統(tǒng)保存在數(shù)據(jù)區(qū)中。 每個(gè)Java虛擬機(jī)的實(shí)現(xiàn)中都包含數(shù)據(jù)區(qū),但是Java虛擬機(jī)規(guī)范對數(shù)據(jù)區(qū)的規(guī)定卻非常的抽象。許多結(jié)構(gòu)上的細(xì)節(jié)部分都留給了 Java虛擬機(jī)實(shí)現(xiàn)者自己發(fā)揮。不同Java虛擬機(jī)實(shí)現(xiàn)上的內(nèi)存結(jié)構(gòu)千差萬別。一部分實(shí)現(xiàn)可能占用很多內(nèi)存,而其他以下可能只占用很少的內(nèi)存;一些實(shí)現(xiàn)可能會使用虛擬內(nèi)存,而其他的則不使用。這種比較精煉的Java虛擬機(jī)內(nèi)存規(guī)約,可以使得Java虛擬機(jī)可以在廣泛的平臺上被實(shí)現(xiàn)。 數(shù)據(jù)區(qū)中的一部分是整個(gè)程序共有,其他部分被單獨(dú)的線程控制。每一個(gè)Java虛擬機(jī)都包含方法區(qū)(method area)和堆(heap),他們都被整個(gè)程序共享。 Java虛擬機(jī)加載并解析一個(gè)類以后,將從類文件中解析出來的信息保存在方法區(qū)(保存解析出的類的信息)中。程序執(zhí)行時(shí)創(chuàng)建的對象都保存在堆(保存對象)中。 當(dāng)一個(gè)線程被創(chuàng)建時(shí),會被分配只屬于他自己的PC寄存器“pc register”(程序計(jì)數(shù)器)和Java堆棧(Java stack)。當(dāng)線程不掉用本地方法時(shí),其作用是,PC寄存器中保存線程執(zhí)行的下一條指令。Java堆棧保存了一個(gè)線程調(diào)用方法時(shí)的狀態(tài),包括本地變量、調(diào)用方法的 參數(shù)、返回值、處理的中間變量。調(diào)用本地方法時(shí)的狀態(tài)保存在本地方法堆棧中,可能在寄存器或者其他非平臺獨(dú)立的內(nèi)存中。 Java堆棧有堆棧塊組成。堆棧塊包含Java方法調(diào)用的狀態(tài)。當(dāng)一個(gè)線程調(diào)用一個(gè)方法時(shí),Java虛擬機(jī)會將一個(gè)新的塊壓到Java堆棧中,當(dāng)這個(gè)方法運(yùn)行結(jié)束時(shí),Java虛擬機(jī)會將對應(yīng)的塊彈出并拋棄。 區(qū)別:Java虛擬機(jī)不使用寄存器保存計(jì)算的中間結(jié)果,而是用Java堆棧在存放中間結(jié)果。這是的Java虛擬機(jī)的指令更緊湊,也更容易在一個(gè)沒有寄存器的設(shè)備上實(shí)現(xiàn)Java虛擬機(jī)。 三、類加載器子系統(tǒng): Java虛擬機(jī)中的類加載器分為兩種:原始類加載器(primordial class loader)和類加載器對象(class loader objects)。特點(diǎn):原始類加載器是Java虛擬機(jī)實(shí)現(xiàn)的一部分,類加載器對象是運(yùn)行中的程序的一部分。不同類加載器加載的類被不同的命名空間所分割。 類加載器調(diào)用了許多Java虛擬機(jī)中其他的部分和java.lang包中的很多類。比如,類加載對象就是java.lang.ClassLoader子類的實(shí)例,ClassLoader類中的方法可以訪問虛擬機(jī)中的類加載機(jī)制。 每一個(gè)被Java虛擬機(jī)加載的類都會被表示為一個(gè)java.lang.Class類的實(shí)例。像其他對象一樣,類加載器對象和Class對象都保存在堆中,被加載的信息被保存在方法區(qū)中。 類加載器所做的工作: 1、加載、連接、初始化(Loading, Linking and Initialization) 類加載子系統(tǒng)不僅僅負(fù)責(zé)定位并加載類文件,他按照以下嚴(yán)格的步驟作了很多其他的事情: 1)、加載:尋找并導(dǎo)入指定類型(類和接口)的二進(jìn)制信息 2)、連接:進(jìn)行驗(yàn)證、準(zhǔn)備和解析 ①驗(yàn)證:確保導(dǎo)入類型的正確性 ②準(zhǔn)備:為類型分配內(nèi)存并初始化為默認(rèn)值 ③解析:將字符引用解析為直接引用 3)、初始化:調(diào)用Java代碼,初始化類變量為合適的值 2、原始類加載器 每個(gè)Java虛擬機(jī)都必須實(shí)現(xiàn)一個(gè)原始類加載器,他能夠加載那些遵守類文件格式并且被信任的類。但是,Java虛擬機(jī)的規(guī)范并沒有定義如何加載類,這由 Java虛擬機(jī)實(shí)現(xiàn)者自己決定。對于給定類型名的類型,原始類加載器必須找到那個(gè)類型名加“.class”的文件并加載入虛擬機(jī)中。 3、類加載器對象 雖然類加載器對象是Java程序的一部分,但是ClassLoader類中的三個(gè)方法可以訪問Java虛擬機(jī)中的類加載子系統(tǒng)。 1)、protected final Class defineClass(…):使用這個(gè)方法可以輸入一個(gè)字節(jié)數(shù)組,定義一個(gè)新的類型。 2)、protected Class findSystemClass(String name):加載指定的類,如果已經(jīng)加載,就直接返回。 3)、protected final void resolveClass(Class c):defineClass方法只是加載一個(gè)類,這個(gè)方法負(fù)責(zé)后續(xù)的動(dòng)態(tài)連接和初始化。 4、命名空間 當(dāng)多個(gè)類加載器加載了同一個(gè)類時(shí),為了保證他們名字的唯一性,需要在類名前加上加載該類的類加載器的標(biāo)識。 四、方法區(qū): 在Java虛擬機(jī)中,被加載類型的信息都保存在方法區(qū)中。這些信息在內(nèi)存中的組織形式由虛擬機(jī)的實(shí)現(xiàn)者定義,比如,虛擬機(jī)工作在一個(gè)“l(fā)ittle- endian”的處理器上,他就可以將信息保存為“l(fā)ittle-endian”格式的,雖然在Java類文件中他們是以“big-endian”格式保存的。設(shè)計(jì)者可以用最適合的表示格式來存儲數(shù)據(jù),以保證程序能夠以最快的速度執(zhí)行。但是,在一個(gè)只有很小內(nèi)存的設(shè)備上,虛擬機(jī)的實(shí)現(xiàn)者就不會占用很大的內(nèi)存。 程序中的所有線程共享一個(gè)方法區(qū),所以訪問方法區(qū)信息的方法必須是線程安全的。如果你有兩個(gè)線程都去加載一個(gè)叫Lava的類,那只能由一個(gè)線程被容許去加載這個(gè)類,另一個(gè)必須等待。 在程序運(yùn)行時(shí),方法區(qū)的大小是可變的,程序在運(yùn)行時(shí)可以擴(kuò)展。有些Java虛擬機(jī)的實(shí)現(xiàn)也可以通過參數(shù)設(shè)定方法區(qū)的初始大小,最小值和最大值。 方法區(qū)也可以被垃圾收集。因?yàn)槌绦蛑械挠深惣虞d器動(dòng)態(tài)加載,所有類可能變成沒有被引用(unreferenced)(雖然已經(jīng)加載進(jìn)來,但是沒有引用?。┑臓顟B(tài)。當(dāng)類變成這種狀態(tài)時(shí),他就可能被垃圾收集掉。沒有加載的類包括兩種狀態(tài),一種是真正的沒有加載,另一個(gè)種是“unreferenced”的狀態(tài)。 1、類型信息(Type Information) 每一個(gè)被加載的類型,在Java虛擬機(jī)中都會在方法區(qū)中保存如下信息: 1)、類型的全名 2)、類型的父類型的全名 3)、給類型是一個(gè)類還是接口 4)、類型的修飾符 5)、 所有父接口全名的列表 類型全名保存的數(shù)據(jù)結(jié)構(gòu)由虛擬機(jī)實(shí)現(xiàn)者定義。除此之外,Java虛擬機(jī)還要為每個(gè)類型保存如下信息: 1)、類型的常量池 2)、類型字段的信息 3)、類型方法的信息 4)、 所有的靜態(tài)類變量(非常量)信息 5)、一個(gè)指向類加載器的引用 6)、一個(gè)指向Class類的引用 7)、類型的常量池 常量池中保存中所有類型是用的有序的常量集合,包含直接常量(literals)如字符串、整數(shù)、浮點(diǎn)數(shù)的常量,和對類型、字段、方法的符號引用。常量池中每一個(gè)保存的常量都有一個(gè)索引,就像數(shù)組中的字段一樣。因?yàn)槌A砍刂斜4嬷兴蓄愋褪褂玫降念愋?、字段、方法的字符引用,所以它也是?dòng)態(tài)連接的主要對象。 2)、類型字段的信息(Field information) 字段名、字段類型、字段的修飾符、字段在類中定義的順序。 3)、類型方法的信息(Method information) 方法名、方法的返回值類型(或者是void)、方法參數(shù)的個(gè)數(shù)、類型和他們的順序、字段的修飾符、方法在類中定義的順序 4)、類(靜態(tài)static)變量 類變量被所有類的實(shí)例共享,即使不通過類的實(shí)例也可以訪問。這些變量綁定在類上(而不是類的實(shí)例上),所以他們是類的邏輯數(shù)據(jù)的一部分。在Java虛擬機(jī)使用這個(gè)類之前就需要為類變量分配內(nèi)存 常量(final)的處理方式于這種類變量不一樣。每一個(gè)類型在用到一個(gè)常量的時(shí)候,都會復(fù)制一份到自己的常量池中。常量也像類變量一樣保存在方法區(qū)中,只不過他保存在常量池中。(可能是,類變量被所有實(shí)例共享,而常量池是每個(gè)實(shí)例獨(dú)有的)。Non-final類變量保存為定義他的類型數(shù)據(jù)的一部分,而final常量保存為使用他的類型數(shù)據(jù)的一部分。 5)、指向類加載器的引用 每一個(gè)被Java虛擬機(jī)加載的類型,虛擬機(jī)必須保存這個(gè)類型是否由原始類加載器或者類加載器加載。那些被類加載器加載的類型必須保存一個(gè)指向類加載器的引用。當(dāng)類加載器動(dòng)態(tài)連接時(shí),會使用這條信息。當(dāng)一個(gè)類引用另一個(gè)類時(shí),虛擬機(jī)必須保存那個(gè)被引用的類型是被同一個(gè)類加載器加載的,這也是虛擬機(jī)維護(hù)不同命名空間的過程。 6)、指向Class類的引用(class.forName的作用) Java虛擬機(jī)為每一個(gè)加載的類型創(chuàng)建一個(gè)java.lang.Class類的實(shí)例。你也可以通過Class類的方法:public static Class forName(String className)來查找或者加載一個(gè)類,并取得相應(yīng)的Class類的實(shí)例。 2、方法列表(Method Tables) 為了更有效的訪問所有保存在方法區(qū)中的數(shù)據(jù),這些數(shù)據(jù)的存儲結(jié)構(gòu)必須經(jīng)過仔細(xì)的設(shè)計(jì)。所有方法區(qū)中,除了保存了上邊的那些原始信息外,還有一個(gè)為了加快存取速度而設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu),比如方法列表。每一個(gè)被加載的非抽象類,Java虛擬機(jī)都會為他們產(chǎn)生一個(gè)方法列表,這個(gè)列表中保存了這個(gè)類可能調(diào)用的所有實(shí)例方法的引用,報(bào)錯(cuò)那些父類中調(diào)用的方法。 五、堆: 當(dāng)Java程序創(chuàng)建一個(gè)類的實(shí)例或者數(shù)組時(shí),都在堆中為新的對象分配內(nèi)存。虛擬機(jī)中只有一個(gè)堆,所有的線程都共享它(方法區(qū)也是可以被共享的?。?。 1、垃圾收集(Garbage Collection) 垃圾收集是釋放沒有被引用的對象的主要方法。它也可能會為了減少堆的碎片,而移動(dòng)對象。在Java虛擬機(jī)的規(guī)范中沒有嚴(yán)格定義垃圾收集,只是定義一個(gè)Java虛擬機(jī)的實(shí)現(xiàn)必須通過某種方式管理自己的堆。 2、對象存儲結(jié)構(gòu)(Object Representation) Java虛擬機(jī)的規(guī)范中沒有定義對象怎樣在堆中存儲。每一個(gè)對象主要存儲的是他的類和父類中定義的對象變量。對于給定的對象的引用,虛擬機(jī)必須能很快的定位到這個(gè)對象的數(shù)據(jù)。另外,必須提供一種通過對象的引用方法對象數(shù)據(jù)的方法,比如方法區(qū)中的對象的引用,所以一個(gè)對象保存的數(shù)據(jù)中往往含有一個(gè)某種形式指向方法區(qū)的指針。 1)一個(gè)可能的堆的設(shè)計(jì)是將堆分為兩個(gè)部分:引用池和對象池。一個(gè)對象的引用就是指向引用池的本地指針。每一個(gè)引用池中的條目都包含兩個(gè)部分:指向?qū)ο蟪刂袑ο髷?shù)據(jù)的指針和方法區(qū)中對象類數(shù)據(jù)的指針。這種設(shè)計(jì)能夠方便Java虛擬機(jī)堆碎片的整理。當(dāng)虛擬機(jī)在對象池中移動(dòng)一個(gè)對象的時(shí)候,只需要修改對應(yīng)引用池中的指針地址。(理解:在引用池中一個(gè)引用就指向了一個(gè)對應(yīng)的對象,移動(dòng)對象的實(shí)質(zhì)就是修改引用池的地址?。┑敲看卧L問對象的數(shù)據(jù)都需要處理兩次指針。 2)另一種堆的設(shè)計(jì)是:一個(gè)對象的引用就是一個(gè)指向一堆數(shù)據(jù)和指向相應(yīng)對象的偏移指針。這種設(shè)計(jì)方便了對象的訪問,可是對象的移動(dòng)要變的異常復(fù)雜。 當(dāng)程序試圖將一個(gè)對象轉(zhuǎn)換為另一種類型時(shí),虛擬機(jī)需要判斷這種轉(zhuǎn)換是否是這個(gè)對象的類型,或者是他的父類型。當(dāng)程序適用instanceof語句的時(shí)候也會做類似的事情。當(dāng)程序調(diào)用一個(gè)對象的方法時(shí),虛擬機(jī)需要進(jìn)行動(dòng)態(tài)綁定,他必須判斷調(diào)用哪一個(gè)類型的方法。這也需要做上面的判斷。 無論虛擬機(jī)實(shí)現(xiàn)者使用哪一種設(shè)計(jì),他都可能為每一個(gè)對象保存一個(gè)類似方法列表的信息。因?yàn)樗梢蕴嵘龑ο蠓椒ㄕ{(diào)用的速度,對提升虛擬機(jī)的性能非常重要,但是虛擬機(jī)的規(guī)范中比沒有要求必須實(shí)現(xiàn)類似的數(shù)據(jù)結(jié)構(gòu)。 每一個(gè)Java虛擬機(jī)中的對象必須關(guān)聯(lián)一個(gè)用于同步多線程lock(mutex)。同一時(shí)刻,只能有一個(gè)對象擁有這個(gè)對象的鎖。當(dāng)一個(gè)擁有這個(gè)對象的鎖,他就可以多次申請這個(gè)鎖,但是也必須釋放相應(yīng)次數(shù)的鎖才能真正釋放這個(gè)對象鎖。很多對象在整個(gè)生命周期中都不會被鎖,所以這個(gè)信息只有在需要時(shí)才需要添加。很多Java虛擬機(jī)的實(shí)現(xiàn)都沒有在對象的數(shù)據(jù)中包含“鎖定數(shù)據(jù)”,只是在需要時(shí)才生成相應(yīng)的數(shù)據(jù)。除了實(shí)現(xiàn)對象的鎖定,每一個(gè)對象還邏輯關(guān)聯(lián)到一 個(gè)“wait set”的實(shí)現(xiàn)。鎖定幫組線程獨(dú)立處理共享的數(shù)據(jù),不需要妨礙其他的線程。“wait set”幫組線程協(xié)作完成同一個(gè)目標(biāo)?!皐ait set”往往通過Object類的wait和notify方法來實(shí)現(xiàn)。 垃圾收集也需要堆中的對象是否被關(guān)聯(lián)的信息。Java虛擬機(jī)規(guī)范中指出垃圾收集一個(gè)運(yùn)行一個(gè)對象的finalize方法一次,但是容許 finalize方法重新引用這個(gè)對象,當(dāng)這個(gè)對象再次不被引用時(shí),就不需要再次調(diào)用finalize方法。所以虛擬機(jī)也需要保存finalize方法 是否運(yùn)行過的信息。 3、數(shù)組的保存(Array Representation) 在Java 中,數(shù)組是一種完全意義上的對象,他和對象一樣保存在堆中、有一個(gè)指向Class類實(shí)例的引用。所有同一維度和類型的數(shù)組擁有同樣的Class,數(shù)組的長度不做考慮。對應(yīng)Class的名字表示為維度和類型數(shù)組必須在堆中保存數(shù)組的長度,數(shù)組的數(shù)據(jù)和一些對象數(shù)組類型數(shù)據(jù)的引用。通過一個(gè)數(shù)組引用的,虛擬機(jī)應(yīng)該能夠取得一個(gè)數(shù)組的長度,通過索引能夠訪問特定的數(shù)據(jù),能夠調(diào)用Object定義的方法。Object是所有數(shù)據(jù)類的直接父類。 |
|