每個(gè)Java程序都運(yùn)行于某個(gè)具體的Java虛擬機(jī)實(shí)現(xiàn)的實(shí)例上。
當(dāng)啟動(dòng)一個(gè)Java程序時(shí),一個(gè)虛擬機(jī)實(shí)例就誕生了,當(dāng)該程序關(guān)閉退出,這個(gè)虛擬機(jī)實(shí)例就隨之消亡。如果同一臺(tái)計(jì)算機(jī)上同時(shí)運(yùn)行3個(gè)Java程序,將得到3個(gè)Java虛擬機(jī)實(shí)例。每個(gè)程序都運(yùn)行于它自己的Java虛擬機(jī)實(shí)例中。
Java虛擬機(jī)實(shí)例通過(guò)調(diào)用某個(gè)初始類(lèi)的main方法來(lái)運(yùn)行一個(gè)Java程序。這個(gè)main方法必須是public,static,接受String[]作為參數(shù),返回void。任何擁有這樣一個(gè)main方法的類(lèi)都可以作為Java程序運(yùn)行的起點(diǎn)。
class Test{
public static void main(String[] args){
//
}
}
告訴Java虛擬機(jī)要運(yùn)行的Java程序中初始類(lèi)的名字后,整個(gè)程序?qū)乃膍ain方法開(kāi)始執(zhí)行。
java Test Hello world
則args[0]="Hello"
,args[1]="world"
main方法是程序初始線(xiàn)程的起點(diǎn),任何其他線(xiàn)程都是由這個(gè)初始線(xiàn)程啟動(dòng)的。
Java虛擬機(jī)內(nèi)部有兩種線(xiàn)程,非守護(hù)線(xiàn)程和守護(hù)線(xiàn)程。
從main開(kāi)始的初始線(xiàn)程是非守護(hù)線(xiàn)程,只要還有任何非守護(hù)線(xiàn)程在運(yùn)行,這個(gè)Java程序就繼續(xù)運(yùn)行。當(dāng)程序中所有的非守護(hù)線(xiàn)程都終止時(shí),虛擬機(jī)實(shí)例將自動(dòng)退出。
守護(hù)線(xiàn)程通常是由虛擬機(jī)自己使用,比如執(zhí)行垃圾收集任務(wù)的線(xiàn)程。Java程序也可以把它創(chuàng)建的任何線(xiàn)程標(biāo)記為守護(hù)線(xiàn)程。
Java虛擬機(jī)體系結(jié)構(gòu)由4部分構(gòu)成:子系統(tǒng),內(nèi)存區(qū),數(shù)據(jù)類(lèi)型,指令。
每個(gè)Java虛擬機(jī)都有一個(gè)類(lèi)裝載器子系統(tǒng),它根據(jù)給定的全限定名來(lái)裝入類(lèi)型。
每個(gè)Java虛擬機(jī)都有一個(gè)執(zhí)行引擎,它負(fù)責(zé)執(zhí)行那些包含在被裝載的方法中的指令。
某些運(yùn)行時(shí)數(shù)據(jù)是由程序中的所有線(xiàn)程共享的,還有一些則只能由一個(gè)線(xiàn)程擁有。
方法區(qū)和堆,是由該虛擬機(jī)實(shí)例中所有線(xiàn)程共享的。
虛擬機(jī)會(huì)從裝載的二進(jìn)制文件中解析類(lèi)型信息放到方法區(qū)中,把該程序運(yùn)行時(shí)創(chuàng)建的對(duì)象都放到堆中。
每一個(gè)新線(xiàn)程被創(chuàng)建時(shí),都將得到它自己的程序指針寄存器(PC)以及一個(gè)Java棧。
如果線(xiàn)程正在執(zhí)行的是一個(gè)Java方法(非本地方法),那么PC指向下一條被執(zhí)行的指令,而Java棧總是存儲(chǔ)該線(xiàn)程中Java方法的調(diào)用狀態(tài)。
本地方法調(diào)用狀態(tài),則是以某種依賴(lài)于具體實(shí)現(xiàn)的方式存儲(chǔ)在本地方法棧中,也可能是寄存器或者其他特定實(shí)現(xiàn)相關(guān)的內(nèi)存區(qū)中。
Java棧由許多幀(frame)組成,幀中包含了Java方法的調(diào)用狀態(tài)。當(dāng)線(xiàn)程調(diào)用一個(gè)Java方法時(shí),虛擬機(jī)壓入一個(gè)新的frame到該線(xiàn)程的Java棧中,該方法返回時(shí),這個(gè)frame從Java棧中彈出并拋棄。
Java虛擬機(jī)為每一個(gè)線(xiàn)程創(chuàng)建的內(nèi)存區(qū)是私有的,任何線(xiàn)程都不能訪(fǎng)問(wèn)其他線(xiàn)程的PC或Java棧。
線(xiàn)程1,2正在執(zhí)行Java方法,它們的PC分別指向下一條將被執(zhí)行的指令。
線(xiàn)程3正在執(zhí)行一個(gè)本地方法,它的PC值是不確定的。
Java虛擬機(jī)中的數(shù)據(jù)類(lèi)型分為兩種:基本類(lèi)型和引用類(lèi)型。
基本類(lèi)型的變量持有原始值,而引用類(lèi)型的變量持有引用值。
關(guān)于基本類(lèi)型boolean,當(dāng)編譯器把Java源碼編譯為字節(jié)碼時(shí),會(huì)用int或byte表示boolean。在Java虛擬機(jī)中,false是用整數(shù)0來(lái)表示的,所有非零整數(shù)都是true。涉及boolean的操作則會(huì)使用int,boolean數(shù)組是當(dāng)做byte數(shù)組訪(fǎng)問(wèn)的。
基本類(lèi)型returnAddress,是只在虛擬機(jī)內(nèi)部使用的基本類(lèi)型,用來(lái)實(shí)現(xiàn)Java程序中的finally字句。
其他基本類(lèi)型都是數(shù)值類(lèi)型,包括整數(shù)類(lèi)型,浮點(diǎn)數(shù)類(lèi)型。
注:
基本類(lèi)型的數(shù)據(jù)不能看作對(duì)象,存放在棧中。基本類(lèi)型都對(duì)應(yīng)有包裝類(lèi),包裝類(lèi)就是對(duì)象了,分配在堆中,棧中保存的是堆內(nèi)對(duì)象的引用。引入包裝類(lèi)的目的是為了讓這些數(shù)據(jù)具有對(duì)象性質(zhì),可以調(diào)用對(duì)象的方法。
引用類(lèi)型有3種:類(lèi)類(lèi)型,接口類(lèi)型,數(shù)組類(lèi)型,還有一種特殊的引用值null。
類(lèi)裝載器子系統(tǒng),負(fù)責(zé)查找并裝載類(lèi)型。
Java虛擬機(jī)有兩種類(lèi)裝載器:啟動(dòng)類(lèi)裝載器和用戶(hù)自定義類(lèi)裝載器。前者是Java虛擬機(jī)實(shí)現(xiàn)的一部分,后者是一個(gè)普通的Java對(duì)象。
由不同的類(lèi)裝載器裝載的類(lèi),會(huì)放在虛擬機(jī)內(nèi)部的不同命名空間中。
對(duì)于每一個(gè)被裝載的類(lèi)型,Java虛擬機(jī)都會(huì)在內(nèi)存堆中為它創(chuàng)建一個(gè)java.lang.Class
類(lèi)的實(shí)例來(lái)代表該類(lèi)型,而裝載的類(lèi)型信息則都位于方法區(qū)。
每個(gè)類(lèi)裝載器都有自己的命名空間,其中維護(hù)著由它裝載的類(lèi)型。所以,一個(gè)Java程序可以多次裝載具有同一個(gè)全限定名的類(lèi)型,類(lèi)型的全限定名不足以確定它在Java虛擬機(jī)中的唯一性。對(duì)于每一個(gè)被裝載的類(lèi)型,Java虛擬機(jī)都會(huì)記錄裝載它的類(lèi)型裝載器。
當(dāng)虛擬機(jī)裝載某個(gè)類(lèi)型時(shí),先使用類(lèi)裝載器定位并讀取相應(yīng)的字節(jié)碼文件,然后提取其中的類(lèi)型信息,將這些類(lèi)型信息和類(lèi)的靜態(tài)變量存儲(chǔ)到方法區(qū)中。
由于所有線(xiàn)程都共享方法區(qū),因此方法區(qū)中的數(shù)據(jù)訪(fǎng)問(wèn)必須被設(shè)計(jì)為線(xiàn)程安全的。假設(shè)同時(shí)有兩個(gè)線(xiàn)程來(lái)訪(fǎng)問(wèn)一個(gè)類(lèi),而這個(gè)類(lèi)還沒(méi)有被裝載,那么,只應(yīng)該有一個(gè)線(xiàn)程去裝載它,另一個(gè)線(xiàn)程等待。
在Java源碼中,全限定名由類(lèi)所屬的包的名稱(chēng)加一個(gè)“.”再加上類(lèi)名組成。
一個(gè)Java程序獨(dú)占一個(gè)Java虛擬機(jī)實(shí)例,每個(gè)Java虛擬機(jī)實(shí)例都有自己的堆空間,堆空間由Java程序的各線(xiàn)程共享。