日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

java中JVM的原理【转】

發(fā)布時(shí)間:2023/11/27 生活经验 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java中JVM的原理【转】 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、java虛擬機(jī)的生命周期:

  Java虛擬機(jī)的生命周期 一個(gè)運(yùn)行中的Java虛擬機(jī)有著一個(gè)清晰的任務(wù):執(zhí)行Java程序。程序開始執(zhí)行時(shí)他才運(yùn)行,程序結(jié)束時(shí)他就停止。你在同一臺(tái)機(jī)器上運(yùn)行三個(gè)程序,就會(huì)有三個(gè)運(yùn)行中的Java虛擬機(jī)。 Java虛擬機(jī)總是開始于一個(gè)main()方法,這個(gè)方法必須是公有、返回void、直接受一個(gè)字符串?dāng)?shù)組。在程序執(zhí)行時(shí),你必須給Java虛擬機(jī)指明這個(gè)包換main()方法的類名。 Main()方法是程序的起點(diǎn),他被執(zhí)行的線程初始化為程序的初始線程。程序中其他的線程都由他來啟動(dòng)。Java中的線程分為兩種:守護(hù)線程 (daemon)和普通線程(non-daemon)。守護(hù)線程是Java虛擬機(jī)自己使用的線程,比如負(fù)責(zé)垃圾收集的線程就是一個(gè)守護(hù)線程。當(dāng)然,你也可 以把自己的程序設(shè)置為守護(hù)線程。包含Main()方法的初始線程不是守護(hù)線程。 只要Java虛擬機(jī)中還有普通的線程在執(zhí)行,Java虛擬機(jī)就不會(huì)停止。如果有足夠的權(quán)限,你可以調(diào)用exit()方法終止程序。

二、java虛擬機(jī)的體系結(jié)構(gòu):

在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)的外部行為。 每一個(gè)Java虛擬機(jī)都由一個(gè)類加載器子系統(tǒng)(class loader subsystem),負(fù)責(zé)加載程序中的類型(類和接口),并賦予唯一的名字。每一個(gè)Java虛擬機(jī)都有一個(gè)執(zhí)行引擎(execution engine)負(fù)責(zé)執(zhí)行被加載類中包含的指令。程序的執(zhí)行需要一定的內(nèi)存空間,如字節(jié)碼、被加載類的其他額外信息、程序中的對(duì)象、方法的參數(shù)、返回值、本地變量、處理的中間變量等等。Java虛擬機(jī)將 這些信息統(tǒng)統(tǒng)保存在數(shù)據(jù)區(qū)(data areas)中。雖然每個(gè)Java虛擬機(jī)的實(shí)現(xiàn)中都包含數(shù)據(jù)區(qū),但是Java虛擬機(jī)規(guī)范對(duì)數(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)可 能會(huì)使用虛擬內(nèi)存,而其他的則不使用。這種比較精煉的Java虛擬機(jī)內(nèi)存規(guī)約,可以使得Java虛擬機(jī)可以在廣泛的平臺(tái)上被實(shí)現(xiàn)。數(shù)據(jù)區(qū)中的一部分是整個(gè)程序共有,其他部分被單獨(dú)的線程控制。每一個(gè)Java虛擬機(jī)都包含方法區(qū)(method area)和堆(heap),他們都被整個(gè)程序共享。Java虛擬機(jī)加載并解析一個(gè)類以后,將從類文件中解析出來的信息保存與方法區(qū)中。程序執(zhí)行時(shí)創(chuàng)建的 對(duì)象都保存在堆中。 當(dāng)一個(gè)線程被創(chuàng)建時(shí),會(huì)被分配只屬于他自己的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)保存在本地方法堆棧中(native method stacks),可能再寄存器或者其他非平臺(tái)獨(dú)立的內(nèi)存中。Java堆棧有堆棧塊(stack frames (or frames))組成。堆棧塊包含Java方法調(diào)用的狀態(tài)。當(dāng)一個(gè)線程調(diào)用一個(gè)方法時(shí),Java虛擬機(jī)會(huì)將一個(gè)新的塊壓到Java堆棧中,當(dāng)這個(gè)方法運(yùn)行結(jié)束時(shí),Java虛擬機(jī)會(huì)將對(duì)應(yīng)的塊彈出并拋棄。Java虛擬機(jī)不使用寄存器保存計(jì)算的中間結(jié)果,而是用Java堆棧在存放中間結(jié)果。這是的Java虛擬機(jī)的指令更緊湊,也更容易在一個(gè)沒有寄存器的設(shè)備上實(shí)現(xiàn)Java虛擬機(jī)。 圖中的Java堆棧中向下增長的,PC寄存器中線程三為灰色,是因?yàn)樗趫?zhí)行本地方法,他的下一條執(zhí)行指令不保存在PC寄存器中。

三、類加載器子系統(tǒng):

Java虛擬機(jī)中的類加載器分為兩種:原始類加載器(primordial class loader)和類加載器對(duì)象(class loader objects)。原始類加載器是Java虛擬機(jī)實(shí)現(xiàn)的一部分,類加載器對(duì)象是運(yùn)行中的程序的一部分。不同類加載器加載的類被不同的命名空間所分割。類加載器調(diào)用了許多Java虛擬機(jī)中其他的部分和java.lang包中的很多類。比如,類加載對(duì)象就是java.lang.ClassLoader子類 的實(shí)例,ClassLoader類中的方法可以訪問虛擬機(jī)中的類加載機(jī)制;每一個(gè)被Java虛擬機(jī)加載的類都會(huì)被表示為一個(gè) java.lang.Class類的實(shí)例。像其他對(duì)象一樣,類加載器對(duì)象和Class對(duì)象都保存在堆中,被加載的信息被保存在方法區(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、原始類加載器(The Primordial Class Loader)每個(gè)Java虛擬機(jī)都必須實(shí)現(xiàn)一個(gè)原始類加載器,他能夠加載那些遵守類文件格式并且被信任的類。但是,Java虛擬機(jī)的規(guī)范并沒有定義如何加載類,這由 Java虛擬機(jī)實(shí)現(xiàn)者自己決定。對(duì)于給定類型名的類型,原始萊加載器必須找到那個(gè)類型名加“.class”的文件并加載入虛擬機(jī)中。3、類加載器對(duì)象雖然類加載器對(duì)象是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)連接和初始化。具體的信息,參見第八章“連接模型”( The Linking Model)。4、命名空間當(dāng)多個(gè)類加載器加載了同一個(gè)類時(shí),為了保證他們名字的唯一性,需要在類名前加上加載該類的類加載器的標(biāo)識(shí)。具體的信息,參見第八章“連接模型”( The Linking Model)。

四、方法區(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ì)者可以用最適合并地機(jī)器的表示格式來存儲(chǔ)數(shù)據(jù),以保證程序能夠以最快的速度執(zhí)行。但是,在一個(gè)只有很小內(nèi)存的設(shè)備上,虛擬機(jī)的實(shí)現(xiàn)者就不會(huì)占用 很大的內(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ù)也訂制方法區(qū)的初始大小,最小值和最大值。方法區(qū)也可以被垃圾收集。因?yàn)槌绦蛑械膬?nèi)由類加載器動(dòng)態(tài)加載,所有類可能變成沒有被引用(unreferenced)的狀態(tài)。當(dāng)類變成這種狀態(tài)時(shí),他就可 能被垃圾收集掉。沒有加載的類包括兩種狀態(tài),一種是真正的沒有加載,另一個(gè)種是“unreferenced”的狀態(tài)。詳細(xì)信息參見第七章的類的生命周期 (The Lifetime of a Class)。1、類型信息(Type Information)每一個(gè)被加載的類型,在Java虛擬機(jī)中都會(huì)在方法區(qū)中保存如下信息:1)、類型的全名(The fully qualified name of the type)2)、類型的父類型的全名(除非沒有父類型,或者弗雷形式j(luò)ava.lang.Object)(The fully qualified name of the typeís direct superclass)3)、給類型是一個(gè)類還是接口(class or an interface)(Whether or not the type is a class )4)、類型的修飾符(public,private,protected,static,final,volatile,transient等)(The typeís modifiers)5)、所有父接口全名的列表(An ordered list of the fully qualified names of any direct superinterfaces)類型全名保存的數(shù)據(jù)結(jié)構(gòu)由虛擬機(jī)實(shí)現(xiàn)者定義。除此之外,Java虛擬機(jī)還要為每個(gè)類型保存如下信息:1)、類型的常量池(The constant pool for the type)2)、類型字段的信息(Field information)3)、類型方法的信息(Method information)4)、所有的靜態(tài)類變量(非常量)信息(All class (static) variables declared in the type, except constants)5)、一個(gè)指向類加載器的引用(A reference to class ClassLoader)6)、一個(gè)指向Class類的引用(A reference to class Class)1)、類型的常量池(The constant pool for the type)常量池中保存中所有類型是用的有序的常量集合,包含直接常量(literals)如字符串、整數(shù)、浮點(diǎn)數(shù)的常量,和對(duì)類型、字段、方法的符號(hào)引用。常量池 中每一個(gè)保存的常量都有一個(gè)索引,就像數(shù)組中的字段一樣。因?yàn)槌A砍刂斜4嬷兴蓄愋褪褂玫降念愋汀⒆侄巍⒎椒ǖ淖址?#xff0c;所以它也是動(dòng)態(tài)連接的主要對(duì) 象。詳細(xì)信息參見第六章“The Java Class File”。2)、類型字段的信息(Field information)字段名、字段類型、字段的修飾符(public,private,protected,static,final,volatile,transient等)、字段在類中定義的順序。3)、類型方法的信息(Method information)方法名、方法的返回值類型(或者是void)、方法參數(shù)的個(gè)數(shù)、類型和他們的順序、字段的修飾符(public,private,protected,static,final,volatile,transient等)、方法在類中定義的順序如果不是抽象和本地本法還需要保存方法的字節(jié)碼、方法的操作數(shù)堆棧的大小和本地變量區(qū)的大小(稍候有詳細(xì)信息)、異常列表(詳細(xì)信息參見第十七章“Exceptions”。)4)、類(靜態(tài))變量(Class Variables)類變量被所有類的實(shí)例共享,即使不通過類的實(shí)例也可以訪問。這些變量綁定在類上(而不是類的實(shí)例上),所以他們是類的邏輯數(shù)據(jù)的一部分。在Java虛擬機(jī)使用這個(gè)類之前就需要為類變量(non-final)分配內(nèi)存常量(final)的處理方式于這種類變量(non-final)不一樣。每一個(gè)類型在用到一個(gè)常量的時(shí)候,都會(huì)復(fù)制一份到自己的常量池中。常量也像類變 量一樣保存在方法區(qū)中,只不過他保存在常量池中。(可能是,類變量被所有實(shí)例共享,而常量池是每個(gè)實(shí)例獨(dú)有的)。Non-final類變量保存為定義他的 類型數(shù)據(jù)(data for the type that declares them)的一部分,而final常量保存為使用他的類型數(shù)據(jù)(data for any type that uses them)的一部分。詳情參見第六章“The Java Class FileThe Java Class File”5)、指向類加載器的引用(A reference to class ClassLoader)每一個(gè)被Java虛擬機(jī)加載的類型,虛擬機(jī)必須保存這個(gè)類型是否由原始類加載器或者類加載器加載。那些被類加載器加載的類型必須保存一個(gè)指向類加載器的引 用。當(dāng)類加載器動(dòng)態(tài)連接時(shí),會(huì)使用這條信息。當(dāng)一個(gè)類引用另一個(gè)類時(shí),虛擬機(jī)必須保存那個(gè)被引用的類型是被同一個(gè)類加載器加載的,這也是虛擬機(jī)維護(hù)不同命 名空間的過程。詳情參見第八章“The Linking Model”6)、指向Class類的引用(A reference to class Class)Java虛擬機(jī)為每一個(gè)加載的類型創(chuàng)建一個(gè)java.lang.Class類的實(shí)例。你也可以通過Class類的方法:
public static Class forName(String className)來查找或者加載一個(gè)類,并取得相應(yīng)的Class類的實(shí)例。通過這個(gè)Class類的實(shí)例,我們可以訪問Java虛擬機(jī)方法區(qū)中的信息。具體參照Class類的JavaDoc。2、方法列表(Method Tables)為了更有效的訪問所有保存在方法區(qū)中的數(shù)據(jù),這些數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)必須經(jīng)過仔細(xì)的設(shè)計(jì)。所有方法區(qū)中,除了保存了上邊的那些原始信息外,還有一個(gè)為了加快存 取速度而設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu),比如方法列表。每一個(gè)被加載的非抽象類,Java虛擬機(jī)都會(huì)為他們產(chǎn)生一個(gè)方法列表,這個(gè)列表中保存了這個(gè)類可能調(diào)用的所有實(shí)例 方法的引用,報(bào)錯(cuò)那些父類中調(diào)用的方法。詳情參見第八章“The Linking Model”

五、堆:

當(dāng)Java程序創(chuàng)建一個(gè)類的實(shí)例或者數(shù)組時(shí),都在堆中為新的對(duì)象分配內(nèi)存。虛擬機(jī)中只有一個(gè)堆,所有的線程都共享他。1、垃圾收集(Garbage Collection)垃圾收集是釋放沒有被引用的對(duì)象的主要方法。它也可能會(huì)為了減少堆的碎片,而移動(dòng)對(duì)象。在Java虛擬機(jī)的規(guī)范中沒有嚴(yán)格定義垃圾收集,只是定義一個(gè)Java虛擬機(jī)的實(shí)現(xiàn)必須通過某種方式管理自己的堆。詳情參見第九章“Garbage Collection”。2、對(duì)象存儲(chǔ)結(jié)構(gòu)(Object Representation)Java虛擬機(jī)的規(guī)范中沒有定義對(duì)象怎樣在堆中存儲(chǔ)。每一個(gè)對(duì)象主要存儲(chǔ)的是他的類和父類中定義的對(duì)象變量。對(duì)于給定的對(duì)象的引用,虛擬機(jī)必須嫩耨很快的 定位到這個(gè)對(duì)象的數(shù)據(jù)。另為,必須提供一種通過對(duì)象的引用方法對(duì)象數(shù)據(jù)的方法,比如方法區(qū)中的對(duì)象的引用,所以一個(gè)對(duì)象保存的數(shù)據(jù)中往往含有一個(gè)某種形式 指向方法區(qū)的指針。一個(gè)可能的堆的設(shè)計(jì)是將堆分為兩個(gè)部分:引用池和對(duì)象池。一個(gè)對(duì)象的引用就是指向引用池的本地指針。每一個(gè)引用池中的條目都包含兩個(gè)部分:指向?qū)ο蟪刂袑?duì) 象數(shù)據(jù)的指針和方法區(qū)中對(duì)象類數(shù)據(jù)的指針。這種設(shè)計(jì)能夠方便Java虛擬機(jī)堆碎片的整理。當(dāng)虛擬機(jī)在對(duì)象池中移動(dòng)一個(gè)對(duì)象的時(shí)候,只需要修改對(duì)應(yīng)引用池中 的指針地址。但是每次訪問對(duì)象的數(shù)據(jù)都需要處理兩次指針。下圖演示了這種堆的設(shè)計(jì)。在第九章的“垃圾收集”中的HeapOfFish Applet演示了這種設(shè)計(jì)。 另一種堆的設(shè)計(jì)是:一個(gè)對(duì)象的引用就是一個(gè)指向一堆數(shù)據(jù)和指向相應(yīng)對(duì)象的偏移指針。這種設(shè)計(jì)方便了對(duì)象的訪問,可是對(duì)象的移動(dòng)要變的異常復(fù)雜。下圖演示了這種設(shè)計(jì) 當(dāng)程序試圖將一個(gè)對(duì)象轉(zhuǎn)換為另一種類型時(shí),虛擬機(jī)需要判斷這種轉(zhuǎn)換是否是這個(gè)對(duì)象的類型,或者是他的父類型。當(dāng)程序適用instanceof語句的時(shí)候也 會(huì)做類似的事情。當(dāng)程序調(diào)用一個(gè)對(duì)象的方法時(shí),虛擬機(jī)需要進(jìn)行動(dòng)態(tài)綁定,他必須判斷調(diào)用哪一個(gè)類型的方法。這也需要做上面的判斷。無論虛擬機(jī)實(shí)現(xiàn)者使用哪一種設(shè)計(jì),他都可能為每一個(gè)對(duì)象保存一個(gè)類似方法列表的信息。因?yàn)樗梢蕴嵘龑?duì)象方法調(diào)用的速度,對(duì)提升虛擬機(jī)的性能非常重要,但 是虛擬機(jī)的規(guī)范中比沒有要求必須實(shí)現(xiàn)類似的數(shù)據(jù)結(jié)構(gòu)。下圖描述了這種結(jié)構(gòu)。圖中顯示了一個(gè)對(duì)象引用相關(guān)聯(lián)的所有的數(shù)據(jù)結(jié)構(gòu),包括:1)、一個(gè)指向類型數(shù)據(jù)的指針2)、一個(gè)對(duì)象的方法列表。方法列表是一個(gè)指向所有可能被調(diào)用對(duì)象方法的指針數(shù)組。方法數(shù)據(jù)包括三個(gè)部分:操作碼堆棧的大小和方法堆棧的本地變量區(qū);方法的字節(jié)碼;異常列表。每一個(gè)Java虛擬機(jī)中的對(duì)象必須關(guān)聯(lián)一個(gè)用于同步多線程的lock(mutex)。同一時(shí)刻,只能有一個(gè)對(duì)象擁有這個(gè)對(duì)象的鎖。當(dāng)一個(gè)擁有這個(gè)這個(gè)對(duì)象 的鎖,他就可以多次申請(qǐng)這個(gè)鎖,但是也必須釋放相應(yīng)次數(shù)的鎖才能真正釋放這個(gè)對(duì)象鎖。很多對(duì)象在整個(gè)生命周期中都不會(huì)被鎖,所以這個(gè)信息只有在需要時(shí)才需 要添加。很多Java虛擬機(jī)的實(shí)現(xiàn)都沒有在對(duì)象的數(shù)據(jù)中包含“鎖定數(shù)據(jù)”,只是在需要時(shí)才生成相應(yīng)的數(shù)據(jù)。除了實(shí)現(xiàn)對(duì)象的鎖定,每一個(gè)對(duì)象還邏輯關(guān)聯(lián)到一 個(gè)“wait set”的實(shí)現(xiàn)。鎖定幫組線程獨(dú)立處理共享的數(shù)據(jù),不需要妨礙其他的線程。“wait set”幫組線程協(xié)作完成同一個(gè)目標(biāo)。“wait set”往往通過Object類的wait()和notify()方法來實(shí)現(xiàn)。 垃圾收集也需要堆中的對(duì)象是否被關(guān)聯(lián)的信息。Java虛擬機(jī)規(guī)范中指出垃圾收集一個(gè)運(yùn)行一個(gè)對(duì)象的finalizer方法一次,但是容許 finalizer方法重新引用這個(gè)對(duì)象,當(dāng)這個(gè)對(duì)象再次不被引用時(shí),就不需要再次調(diào)用finalize方法。所以虛擬機(jī)也需要保存finalize方法 是否運(yùn)行過的信息。更多信息參見第九章的“垃圾收集”3、數(shù)組的保存(Array Representation)
在Java 中,數(shù)組是一種完全意義上的對(duì)象,他和對(duì)象一樣保存在堆中、有一個(gè)指向Class類實(shí)例的引用。所有同一維度和類型的數(shù)組擁有同樣的Class,數(shù)組的長 度不做考慮。對(duì)應(yīng)Class的名字表示為維度和類型。比如一個(gè)整型數(shù)據(jù)的Class為“[I”,字節(jié)型三維數(shù)組Class名為“[[[B”,兩維對(duì)象數(shù)據(jù) Class名為“[[Ljava.lang.Object”。數(shù)組必須在堆中保存數(shù)組的長度,數(shù)組的數(shù)據(jù)和一些對(duì)象數(shù)組類型數(shù)據(jù)的引用。通過一個(gè)數(shù)組引用的,虛擬機(jī)應(yīng)該能夠取得一個(gè)數(shù)組的長度,通過索引能夠訪問特定 的數(shù)據(jù),能夠調(diào)用Object定義的方法。Object是所有數(shù)據(jù)類的直接父類。更多信息參見第六章“類文件”。

六、基本結(jié)構(gòu):

從Java平臺(tái)的邏輯結(jié)構(gòu)上來看,我們可以從下圖來了解JVM:

?

從上圖能清晰看到Java平臺(tái)包含的各個(gè)邏輯模塊,也能了解到JDK與JRE的區(qū)別。

JVM自身的物理結(jié)構(gòu)

?

此圖看出jvm內(nèi)存結(jié)構(gòu)

JVM內(nèi)存結(jié)構(gòu)主要包括兩個(gè)子系統(tǒng)和兩個(gè)組件。兩個(gè)子系統(tǒng)分別是Classloader子系統(tǒng)和Executionengine(執(zhí)行引擎)子系統(tǒng);兩個(gè)組件分別是Runtimedataarea(運(yùn)行時(shí)數(shù)據(jù)區(qū)域)組件和Nativeinterface(本地接口)組件。

Classloader子系統(tǒng)的作用:

根據(jù)給定的全限定名類名(如java.lang.Object)來裝載class文件的內(nèi)容到Runtimedataarea中的methodarea(方法區(qū)域)。Java程序員可以extendsjava.lang.ClassLoader類來寫自己的Classloader。

Executionengine子系統(tǒng)的作用:

執(zhí)行classes中的指令。任何JVMspecification實(shí)現(xiàn)(JDK)的核心都是Executionengine,不同的JDK例如Sun的JDK和IBM的JDK好壞主要就取決于他們各自實(shí)現(xiàn)的Executionengine的好壞。

Nativeinterface組件:

與nativelibraries交互,是其它編程語言交互的接口。當(dāng)調(diào)用native方法的時(shí)候,就進(jìn)入了一個(gè)全新的并且不再受虛擬機(jī)限制的世界,所以也很容易出現(xiàn)JVM無法控制的nativeheapOutOfMemory。

RuntimeDataArea組件:

這就是我們常說的JVM的內(nèi)存了。它主要分為五個(gè)部分——

1、Heap(堆):一個(gè)Java虛擬實(shí)例中只存在一個(gè)堆空間

2、MethodArea(方法區(qū)域):被裝載的class的信息存儲(chǔ)在Methodarea的內(nèi)存中。當(dāng)虛擬機(jī)裝載某個(gè)類型時(shí),它使用類裝載器定位相應(yīng)的class文件,然后讀入這個(gè)class文件內(nèi)容并把它傳輸?shù)教摂M機(jī)中。

3、JavaStack(java的棧):虛擬機(jī)只會(huì)直接對(duì)Javastack執(zhí)行兩種操作:以幀為單位的壓棧或出棧

4、ProgramCounter(程序計(jì)數(shù)器):每一個(gè)線程都有它自己的PC寄存器,也是該線程啟動(dòng)時(shí)創(chuàng)建的。PC寄存器的內(nèi)容總是指向下一條將被執(zhí)行指令的餓地址,這里的地址可以是一個(gè)本地指針,也可以是在方法區(qū)中相對(duì)應(yīng)于該方法起始指令的偏移量。

5、Nativemethodstack(本地方法棧):保存native方法進(jìn)入?yún)^(qū)域的地址

?

對(duì)于JVM的學(xué)習(xí),在我看來這么幾個(gè)部分最重要:

  • Java代碼編譯和執(zhí)行的整個(gè)過程
  • JVM內(nèi)存管理及垃圾回收機(jī)制

?

Java代碼編譯和執(zhí)行的整個(gè)過程

Java代碼編譯是由Java源碼編譯器來完成,流程圖如下所示:

?

?

Java字節(jié)碼的執(zhí)行是由JVM執(zhí)行引擎來完成,流程圖如下所示:

?

?

Java代碼編譯和執(zhí)行的整個(gè)過程包含了以下三個(gè)重要的機(jī)制:

  • Java源碼編譯機(jī)制
  • 類加載機(jī)制
  • 類執(zhí)行機(jī)制

Java源碼編譯機(jī)制

Java 源碼編譯由以下三個(gè)過程組成:(javac –verbose ?輸出有關(guān)編譯器正在執(zhí)行的操作的消息)

  • 分析和輸入到符號(hào)表
  • 注解處理
  • 語義分析和生成class文件

?

最后生成的class文件由以下部分組成:

  • 結(jié)構(gòu)信息。包括class文件格式版本號(hào)及各部分的數(shù)量與大小的信息
  • 元數(shù)據(jù)。對(duì)應(yīng)于Java源碼中聲明與常量的信息。包含類/繼承的超類/實(shí)現(xiàn)的接口的聲明信息、域與方法聲明信息和常量池
  • 方法信息。對(duì)應(yīng)Java源碼中語句和表達(dá)式對(duì)應(yīng)的信息。包含字節(jié)碼、異常處理器表、求值棧與局部變量區(qū)大小、求值棧的類型記錄、調(diào)試符號(hào)信息

類加載機(jī)制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關(guān)系和加載順序可以由下圖來描述:

?

1)Bootstrap ClassLoader /啟動(dòng)類加載器

?

$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實(shí)現(xiàn),不是ClassLoader子類

?

2)Extension ClassLoader/擴(kuò)展類加載器

?

負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

?

3)App ClassLoader/ 系統(tǒng)類加載器

?

負(fù)責(zé)記載classpath中指定的jar包及目錄中class

?

4)Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)

?

屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader

?

加載過程中會(huì)先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個(gè)classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

?

?

類加載雙親委派機(jī)制介紹和分析

在這里,需要著重說明的是,JVM在加載類時(shí)默認(rèn)采用的是雙親委派機(jī)制。通俗的講,就是某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時(shí),才自己去加載。

?

類執(zhí)行機(jī)制

  JVM是基于棧的體系結(jié)構(gòu)來執(zhí)行class字節(jié)碼的。線程創(chuàng)建后,都會(huì)產(chǎn)生程序計(jì)數(shù)器(PC)和棧(Stack),程序計(jì)數(shù)器存放下一條要執(zhí)行的指令在方法內(nèi)的偏移量,棧中存放一個(gè)個(gè)棧幀,每個(gè)棧幀對(duì)應(yīng)著每個(gè)方法的每次調(diào)用,而棧幀又是有局部變量區(qū)和操作數(shù)棧兩部分組成,局部變量區(qū)用于存放方法中的局部變量和參數(shù),操作數(shù)棧中用于存放方法執(zhí)行過程中產(chǎn)生的中間結(jié)果。

內(nèi)存管理和垃圾回收

JVM內(nèi)存組成結(jié)構(gòu)

JVM棧由堆、棧、本地方法棧、方法區(qū)等部分組成,結(jié)構(gòu)圖如下所示:

?

?

?

JVM內(nèi)存回收

?

Sun的JVMGenerationalCollecting(垃圾回收)原理是這樣的:把對(duì)象分為年青代(Young)、年老代(Tenured)、持久代(Perm),對(duì)不同生命周期的對(duì)象使用不同的算法。(基于對(duì)對(duì)象生命周期分析)

?

1.Young(年輕代)

年輕代分三個(gè)區(qū)。一個(gè)Eden區(qū),兩個(gè)Survivor區(qū)。大部分對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí),還存活的對(duì)象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè)),當(dāng)這個(gè)Survivor區(qū)滿時(shí),此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū),當(dāng)這個(gè)Survivor去也滿了的時(shí)候,從第一個(gè)Survivor區(qū)復(fù)制過來的并且此時(shí)還存活的對(duì)象,將被復(fù)制年老區(qū)(Tenured。需要注意,Survivor的兩個(gè)區(qū)是對(duì)稱的,沒先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí)存在從Eden復(fù)制過來對(duì)象,和從前一個(gè)Survivor復(fù)制過來的對(duì)象,而復(fù)制到年老區(qū)的只有從第一個(gè)Survivor去過來的對(duì)象。而且,Survivor區(qū)總有一個(gè)是空的。

2.Tenured(年老代)

年老代存放從年輕代存活的對(duì)象。一般來說年老代存放的都是生命期較長的對(duì)象。

3.Perm(持久代)

用于存放靜態(tài)文件,如今Java類、方法等。持久代對(duì)垃圾回收沒有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來存放這些運(yùn)行過程中新增的類。持久代大小通過-XX:MaxPermSize=進(jìn)行設(shè)置。

舉個(gè)例子:當(dāng)在程序中生成對(duì)象時(shí),正常對(duì)象會(huì)在年輕代中分配空間,如果是過大的對(duì)象也可能會(huì)直接在年老代生成(據(jù)觀測在運(yùn)行某程序時(shí)候每次會(huì)生成一個(gè)十兆的空間用收發(fā)消息,這部分內(nèi)存就會(huì)直接在年老代分配)。年輕代在空間被分配完的時(shí)候就會(huì)發(fā)起內(nèi)存回收,大部分內(nèi)存會(huì)被回收,一部分幸存的內(nèi)存會(huì)被拷貝至Survivor的from區(qū),經(jīng)過多次回收以后如果from區(qū)內(nèi)存也分配完畢,就會(huì)也發(fā)生內(nèi)存回收然后將剩余的對(duì)象拷貝至to區(qū)。等到to區(qū)也滿的時(shí)候,就會(huì)再次發(fā)生內(nèi)存回收然后把幸存的對(duì)象拷貝至年老區(qū)。

通常我們說的JVM內(nèi)存回收總是在指堆內(nèi)存回收,確實(shí)只有堆中的內(nèi)容是動(dòng)態(tài)申請(qǐng)分配的,所以以上對(duì)象的年輕代和年老代都是指的JVM的Heap空間,而持久代則是之前提到的MethodArea,不屬于Heap。

關(guān)于JVM內(nèi)存管理的一些建議

1、手動(dòng)將生成的無用對(duì)象,中間對(duì)象置為null,加快內(nèi)存回收。

2、對(duì)象池技術(shù)如果生成的對(duì)象是可重用的對(duì)象,只是其中的屬性不同時(shí),可以考慮采用對(duì)象池來較少對(duì)象的生成。如果有空閑的對(duì)象就從對(duì)象池中取出使用,沒有再生成新的對(duì)象,大大提高了對(duì)象的復(fù)用率。

3、JVM調(diào)優(yōu)通過配置JVM的參數(shù)來提高垃圾回收的速度,如果在沒有出現(xiàn)內(nèi)存泄露且上面兩種辦法都不能保證JVM內(nèi)存回收時(shí),可以考慮采用JVM調(diào)優(yōu)的方式來解決,不過一定要經(jīng)過實(shí)體機(jī)的長期測試,因?yàn)椴煌膮?shù)可能引起不同的效果。如-Xnoclassgc參數(shù)等。

轉(zhuǎn)載于:https://www.cnblogs.com/wojiaochuichui/p/9505062.html

總結(jié)

以上是生活随笔為你收集整理的java中JVM的原理【转】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。