Java虚拟机是什么
http://boy00fly.iteye.com/blog/1095263
要理解java虛擬機,你首先必須意識到,當你說“Java虛擬機”時,可能指的是如下三個不同的東西:
1. 抽象規(guī)范
2.?一個具體的實現(xiàn)
3.?一個運行中的虛擬機實例
?
?? ? ? ? ? Java虛擬機抽象規(guī)范僅僅是一個概念,在Tim Lindholm和Frank Yellin編著的《The Java Virtual Machine Specification》一書中詳細地描述了它。而該規(guī)范的具體實現(xiàn),可能來自多個提供商,并存在于多個平臺。他或者完全用軟件實現(xiàn),或者以硬件和軟件結(jié)合的方式來實現(xiàn)。當運行一個Java程序的同時,也就是運行了一個Java虛擬機實例。每個Java程序都運行于某個具體的Java虛擬機實現(xiàn)的實例上。
?
Java虛擬機的生命周期
?
??一個運行時的Java虛擬機實例的天職就是:負責運行一個Java程序。當啟動一個Java程序時,一個虛擬機實例也就誕生了。當該程序關(guān)閉退出是,這個虛擬機實例也就隨之消亡。如果在同一個計算機上同時運行三個程序,將得到三個Java虛擬機實例。每個Java程序都運行于它自己的Java虛擬機實例中。
??Java虛擬機實例通過調(diào)用某個初始類的main()方法來運行一個Java程序,此方法將作為該程序初始線程的啟動,任何其他的線程都是由這個初始線程啟動的。
??在Java虛擬機內(nèi)部有兩種線程:守護線程與非守護線程。守護線程通常是由虛擬機自己使用的,比如執(zhí)行垃圾收集任務的線程。但是,Java程序也可以把他創(chuàng)建的任何線程標記為守護線程。而Java程序中的初始線程---------就是開始于main()的那個,是非守護進程。只要還有非守護線程在運行,那么這個Java程序也在繼續(xù)運行(虛擬機仍然存活)。當該程序中所有的非守護線程都終止時,虛擬機實例將自動退出。假若安全管理器允許,程序本身也能夠通過調(diào)用Runtime類或者System類的exit()方法來退出。
?
Java虛擬機體系結(jié)構(gòu)
從上圖可以看得出來,JVM中包含:
?
?1.?Class Loader 類加載器
?
?? 所有的class文件必須被加載后才能在jvm中運行,關(guān)于jvm的類加載器,在其他章節(jié)中重點描述。
?
2. ?Runtime Data Areas 運行數(shù)據(jù)區(qū)
?
?? ? 運行時數(shù)據(jù)區(qū)分為:method area(本地方法區(qū))、heap(堆)、java stacks(Java 棧)、 pc registers(pc寄存器)、 native method stacks(本地方法棧)
?
?? ? 下面來依次解釋下上述內(nèi)容:
?
?? Method area(方法區(qū))
?
在Java虛擬機中,關(guān)于被裝載類型的信息存儲在一個邏輯上被稱為方法區(qū)的內(nèi)存中。當虛擬機狀態(tài)某個類型時,它使用類裝載器定位相應的class文件,然后讀入這個class文件------一個線性二進制數(shù)據(jù)流。然后將它傳輸?shù)教摂M機中。緊接著虛擬機提取其中的類型信息,并將這些信息存儲到方法區(qū)。該類型中的類(靜態(tài))變量同樣也是存儲在方法區(qū)中。
?
Java虛擬機在內(nèi)部如何存儲類型信息,這是由具體實現(xiàn)的設計者來決定的。比如,在class文件中,多字節(jié)總是以高位在前(即代表較大數(shù)的字節(jié)在前)的順序儲存。但是這些數(shù)據(jù)被引入到方法區(qū)后,虛擬機可以以任何方式存儲它。假設某個實現(xiàn)是運行在低位優(yōu)先的處理器上,那么它很可能會把多字節(jié)以低位優(yōu)先的順序存儲到方法區(qū)中。
?
當虛擬機運行Java程序時,它會查找使用存儲在方法區(qū)中的類型信息。設計者應當為類型信息的內(nèi)部設計適當?shù)臄?shù)據(jù)結(jié)構(gòu),以盡可能在保持虛擬機小巧緊湊的同事加快程序的運行效率。如果正在設計一個需要在少量內(nèi)存的限制中操作的實現(xiàn),設計則可能會決定以犧牲某些運行速度來換取緊湊性。另外一方面,如果設計一個將在虛擬內(nèi)存系統(tǒng)中運行的實現(xiàn),設計者可能會決定在方法去中保存一些冗余的信息,以此來加快執(zhí)行 速度。(如果底層主機沒有提供虛擬內(nèi)存,但是提供了一個硬盤,設計者可能會在實現(xiàn)中穿件一個虛擬內(nèi)存系統(tǒng)。)Java虛擬機的設計者可以根據(jù)目標平臺的資源限制和需求,在空間和時間上作出權(quán)衡,選擇實現(xiàn)什么樣的數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)組織。
?
由于所有線程都共享方法區(qū),因此它們對方法區(qū)數(shù)據(jù)的訪問必須被設計為是線程安全的。比如,假設同時有連個線程都企圖訪問一個名為Lava的類,而這個類還沒有被裝入虛擬機,那么這是只應該有一個線程去裝載它,而另外一個線程則只能等待。
?
方法區(qū)的大小不必是固定的,虛擬機可以根據(jù)應用的需要動態(tài)調(diào)整。同樣,方法區(qū)也不必是連續(xù)的,方法區(qū)可以在一個堆(甚至是虛擬機自己的堆)中自由分配。另外,虛擬機也可以允許用戶或者程序員指定方法區(qū)的初始化大小以及最小和最大尺寸等。
?
方法區(qū)也可以被垃圾收集,因為虛擬機允許通過用戶定義的類裝載器來動態(tài)擴展java程序,因此一些類也會成為程序"不再引用"的類。當某個類變?yōu)椴辉俦灰玫念悤r,Java虛擬機可以卸載這個類(垃圾收集),從而使方法區(qū)占據(jù)的內(nèi)存保持最小。
?
方法區(qū)針對具體的語言特性有幾種信息是存儲在方法區(qū)內(nèi)的:
【類型信息】 --對每個裝載的類型,虛擬機都會在方法區(qū)中存儲以下類型信息
?? ?這個類型的完全限定名(java.lang.String格式)
?? ?這個類型的直接超類的全限定名(除非uzhege類型時java.lang.Object,它沒有超類)
?? ?這個類型是類類型還是接口類型
?? ?這個類型的訪問修飾符(public、abstract或final的某個子集)
?? ?任何直接超接口的全限定名的有序列表
?
?在Java class文件和虛擬機中,類型名總是以全限定名出現(xiàn)。在java源代碼中,全限定名由類所屬包的名稱加上一個“.”,再加上類名組成。例如,類Object 的所屬包為java.lang,那她的全限定名應該是?java.lang.Object,但是在class文件里,所有的“.”都被斜杠“/”替換,這樣就成為java/lang/Object。至于全限定名在方法區(qū)中的表示,則因不同的設計者有不同的選擇而不同,可以用任何形式和數(shù)據(jù)結(jié)構(gòu)來表示。
?
除了上面列出的基本類型信息外,虛擬機還得為每個被裝載的類型存儲以下信息:
【該類型的常量池】
【字段信息】
【方法信息】
【除了常量意外的所有類(靜態(tài))變量】
【一個到ClassLoader的引用】
【一個到Class類的引用】
?
?【常量池】
虛擬機必須為每一個被裝載的類型維護一個常量池。常量池就是該類型所用常量的一個有序集合,包括直接常量(string,integer和floating point常量)和對其他類型、字段和方法的符號引用。池中的數(shù)據(jù)項就像數(shù)組一樣是通過索引訪問的。因為常量池存儲了相應類型所用到的所有類型、字段和方法的符號引用,所以它在java程序的動態(tài)連接中起著核心的作用。
?
【字段信息】
對于類型中聲明的沒一個字段,方法區(qū)中必須保存下面的信息。除此之外,這些字段在類或者接口中的聲明順序也必須保存。下面是字段信息的清單:
字段名
字段的類型
字段的修飾符(public、private、protected、static、final、volatile、transient的某個子集)
?
?
【方法信息】
對于類型中聲明的每一個方法,方法區(qū)中必須保存下面的信息。和字段一樣,這些方法法在類或者接口中的聲明順序也必須保存。下面試方法信息的清單:
方法名
方法的返回類型(或void)
方法參數(shù)的數(shù)量和類型(按按聲明順序)
方法的修飾符(public、private、protected、static、final、synchronized、native、abstract的某個子集)
?
除了上面的清單中列出的條目之外,如果某個方法不是抽象的和本地的,它還必須保存下面的信息:
方法的字節(jié)碼(bytecodes)
操作數(shù)棧和該方法的棧幀中的局部變量區(qū)的大小
異常表
?
【類(靜態(tài))變量】
類變量是由所有類實例共享的,即使沒有任何類實例,它也可以被訪問。這些變量只與類有關(guān)---而非類的實例,因此他們總是作為類型信息的一部分二存儲在方法區(qū)。除了在類中聲明的編譯時常量外,虛擬機在使用某個類之前,必須在方法區(qū)中為這些類變量分配空間。
?
而編譯時常量(就是那些用final聲明以及用編譯時已知的值初始化的類變量)則和一般的類變量的處理方式不同,每個使用編譯時常量的類型都會復制它的所有常量到自己的常量池中,或嵌入到它的字節(jié)碼流中。作為 常量池或字節(jié)碼流的一部分,編譯時常量保存在方法區(qū)中,就和一般的類變量一樣。但是當一般的類變量做為聲明他們的類型的一部分數(shù)據(jù)面保存的時候,編譯時常量作為使用他們的類型的一部分而保存。
?
【指向ClassLoader類的應用】
每個類型被加載的時候,虛擬機必須跟蹤它是由啟動類裝載器還是由用戶自定義類裝載器裝載的。如果是用戶自定義類裝載器裝載的,那么虛擬機必須在類型信息中存儲對該裝載器的引用。這是作為方法表中的類型數(shù)據(jù)的一部分保存的。虛擬機會在動態(tài)連接期間使用這個信息。當某個類型引用另外一個類型的時候,虛擬機會請求裝載發(fā)起引用類型的類裝載器類裝載來裝載被引用的類型。這個動態(tài)連接的過程,對于虛擬機分離命名空間的范式也是至關(guān)重要的。為了能夠正確地執(zhí)行動態(tài)連接以及維護多個命名空間,虛擬機需要在方法表中得知每個類都是由哪個類裝載器裝載的,
?
【指向Class類的引用】
對于每個被裝載的類型(不管是類還是接口),虛擬機都會相應地位它創(chuàng)建一個java.lang.Class類的實例,而且虛擬機還必須以某種方式吧這個實例和存儲在方法區(qū)中的數(shù)據(jù)關(guān)聯(lián)起來。
?
?
【方法表】
為了盡可能提高訪問效率,設計者必須仔細設計存儲在方法區(qū)中的類型信息的數(shù)據(jù)結(jié)構(gòu),因此,除了以上討論的原始類型信息,實現(xiàn)中還可能包括其他數(shù)據(jù)結(jié)構(gòu)加快訪問原始數(shù)據(jù)的速度,比如方法表。虛擬機對每個裝載的非抽象類,都生成一個方法表,把它作為類信息的一部分保存在方法區(qū)。方法表是一個數(shù)組,他的元素是所有他的實例可能被調(diào)用的實例方法的直接引用,包括哪些從超類繼承過來的實例方法。(對于抽象類和接口,方法表沒有什么幫助,因為程序絕不會生成他們的實例)運行時可以通過方法表快速搜索在對象中調(diào)用的實例方法。
?
方法區(qū)使用示例
為了展示虛擬機如何使用方法區(qū)中的信息,我們舉個例子,看下面這個類
Java代碼??? ? ?下面的段落描述了某個實現(xiàn)是如何執(zhí)行Volcano程序中main()方法的字節(jié)碼中第一條指令的。不同的虛擬機實現(xiàn)可能會用完全不同的方法來操作,下面描述的只是其中一種可能,但是并不是僅有的一種,下面看一下Java虛擬機是如何執(zhí)行Volcano程序中main()方法的第一條指令的。
?
?? ?要運行Volcano程序,首先得以某種“依賴于實現(xiàn)的”方式告訴虛擬機“Volcano”這個名字。之后虛擬機將找到并讀入相應的class文件“Volcano.class”,然后他會從導入的class文件里的而精致數(shù)據(jù)中提取類型信息并放到方法區(qū)中。通過執(zhí)行保存在方法區(qū)中額字節(jié)碼,虛擬機開始執(zhí)行main()方法,在執(zhí)行時,他會一直持有指向當前類(Volcano類)的常量池(方法區(qū)中的一個數(shù)據(jù)結(jié)構(gòu))的指針。
?
?? ?注意,虛擬機開始執(zhí)行Volcano類中main()方法的字節(jié)碼的時候,盡管Lava類還沒被裝載,但是和大多數(shù)(也許是所有)?? ?虛擬機實現(xiàn)一樣,他不會等到把程序中用到的所有類都裝載后才開始運行程序。恰好相反,他只需在需要是才裝載相應的類。
?
?? ? main()的第一條指令告知虛擬機為列在常量池第一項的類分配足夠的內(nèi)存。所以虛擬機使用指向Volcano常量池的指針找到第一項,發(fā)現(xiàn)他是一個堆Lava類的符號引用,然后他就檢查方法區(qū),看Lava類是否已經(jīng)被裝載了。
?
?? ??這個符號引用僅僅是一個給出了類Lava的全限定名“Lava”的字符串。為了能讓虛擬機盡可能快地從一個名稱找到類,設計者應當選擇最佳的數(shù)據(jù)結(jié)構(gòu)和算法。這里可以采用各種方法,如散列表、搜索樹等等。同樣的算法可以以用于實現(xiàn)Class類的forName()方法,這個方法根據(jù)給定的全限定名返回Class引用。
?
?? ?當虛擬機發(fā)現(xiàn)還沒有裝載過名為“Lava”的類時,他就開始查找并裝載文件“Lava.class”,并把從讀入的二進制數(shù)據(jù)中提取的類型信息放在方法區(qū)中。
?
?? ?緊接著,虛擬機以一個直接指向方法區(qū)Lava類數(shù)據(jù)的指針類替換常量池第一項(就是那個字符串“Lava”)----以后就可以用這個指針來快速訪問Lava類了。這個替換過程稱為常量池解析,即把常量池中的符號引用替換為直接引用。這是通過在方法區(qū)中搜索被引用的元素實現(xiàn)的,在這期間可能又需要裝載其他類。在這里,我們替換掉符號引用的“直接引用”是一個本地指針。
?
?? ? 終于,虛擬機轉(zhuǎn)變?yōu)橐粋€新的Lava對象分配內(nèi)存。此時,它又需要方法區(qū)中的信息。還記得剛剛放到Volcano類常量池第一項的指針嗎?現(xiàn)在虛擬機用它來訪問Lava類型信息(此前剛放到方法區(qū)中的),找到其中記錄的這樣一個信息:一個Lava對象需要分配多少堆空間。
?
?? ? Java虛擬機總能夠通過存儲于方法區(qū)的類型信息來實現(xiàn)一個對象需要的內(nèi)存,但是,某一個特定對象事實上需要多少內(nèi)存,是跟特定實現(xiàn)相關(guān)的。對象在虛擬機內(nèi)部的表示由實現(xiàn)的設計者來決定的。
?
?? ? 當java虛擬機確定了一個Lava對象的大小后,它就在堆上分配這么大的空間,并把這個對象實例的變量speed初始化為默認初始值0.假如Lava類的超類Object也有實例變量,這也會在此時被初始化為相應的默認值。
?
?? ? 當把新生成的Lava對象的引用壓到棧中,main()方法的第一條指令也完成了。接下來的指令通過這個引用調(diào)用Java代碼(改代碼把speed變量初始化為爭取的初始值5)。另外一條指令將用這個引用調(diào)用Lava對 象引用的flow()方法。
??
native method library(本地方法)
就目前的理解就是jni的調(diào)用
總結(jié)
以上是生活随笔為你收集整理的Java虚拟机是什么的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java里的堆(heap)栈(stack
- 下一篇: JAVA并发编程学习笔记之CAS操作