深入理解Java虛擬機(jī)學(xué)習(xí)筆記_第1頁(yè)
深入理解Java虛擬機(jī)學(xué)習(xí)筆記_第2頁(yè)
深入理解Java虛擬機(jī)學(xué)習(xí)筆記_第3頁(yè)
深入理解Java虛擬機(jī)學(xué)習(xí)筆記_第4頁(yè)
深入理解Java虛擬機(jī)學(xué)習(xí)筆記_第5頁(yè)
已閱讀5頁(yè),還剩12頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、JVM的自動(dòng)內(nèi)存管理機(jī)制一 如何劃分JVM內(nèi)存JVM所管理的內(nèi)存在運(yùn)行時(shí)會(huì)被分為這樣幾個(gè)數(shù)據(jù)區(qū):虛擬機(jī)棧區(qū),堆區(qū),方法區(qū),本地方法棧,程序計(jì)數(shù)器。程序計(jì)數(shù)器是一個(gè)比較小的內(nèi)存區(qū)域,用于指示當(dāng)前線程所執(zhí)行的字節(jié)碼執(zhí)行到了第幾行,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間程序計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),是線程隔離的。程序計(jì)數(shù)器所在的內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域。虛擬機(jī)棧,線程私有,它的生命周期與線程相同。虛擬機(jī)棧區(qū)描述的是Java方法執(zhí)行內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出

2、口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程。局部變量表存放了8種基本數(shù)據(jù)類型、對(duì)象的引用和returnAddress。局部變量表所需的內(nèi)存空間在編譯期間完成分配,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。本地方法棧,作用與虛擬機(jī)棧區(qū)是相似的,他們之間的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。堆,Java堆,也稱GC堆,是最大的一塊,是被線程共享的區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。所有類的實(shí)例(對(duì)象)和數(shù)組都是在堆上分配內(nèi)存的,堆內(nèi)存由存活和死亡的對(duì)象,空閑碎片區(qū)組成,對(duì)象所占的堆內(nèi)存是由自動(dòng)內(nèi)存管

3、理系統(tǒng)回收。(數(shù)組是一種對(duì)象)從內(nèi)存回收角度來(lái)看,Java堆還可以細(xì)分為新生代和老年代;甚至還可以分為Eden空間、 From Survivor空間、To Survivor空間等。從內(nèi)存分配角度來(lái)看,線程共享的Java堆中可能劃分出多個(gè)線程私有的分配緩沖區(qū)(TLAB)。Java堆可以處于物理上不連續(xù)的內(nèi)存中,只要邏輯上連續(xù)即可。方法區(qū)在JVM中也是一個(gè)非常重要的區(qū)域,在HotSpot虛擬機(jī)上,方法區(qū)被稱為“永久代”。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆區(qū)的一個(gè)邏輯部分,但還是要區(qū)分來(lái)對(duì)待。方法區(qū)用于存儲(chǔ)已被JVM加載的類信息(包括類的名稱、方法信息、字段信息)、類變量(靜態(tài)變量)、常量、即時(shí)

4、編譯器編譯后的代碼等數(shù)據(jù)。雖然方法區(qū)中有些數(shù)據(jù)是線程隔離的,但是編譯器編譯后的代碼等數(shù)據(jù),是線程共享的。除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以固定大小或者可擴(kuò)展外,還可以選擇不實(shí)現(xiàn)垃圾收集。但不并非方法區(qū)就不要內(nèi)存回收了,方法區(qū)的內(nèi)存回收只要針對(duì)常量池的回收和對(duì)類型的卸載。運(yùn)行時(shí)常量池,是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯期生成的各種字面量和符號(hào)引用。二 對(duì)象的創(chuàng)建在語(yǔ)言層面上,創(chuàng)建對(duì)象通常僅僅是一個(gè)new關(guān)鍵字而已。但在虛擬機(jī)中,對(duì)象的創(chuàng)建過(guò)程大致分為以下四步:第一步,檢查類加載。虛擬機(jī)遇到一條new指令時(shí),首

5、先需要去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過(guò)。如果沒(méi)有,那必須先執(zhí)行相應(yīng)的類加載過(guò)程。第二步,分配內(nèi)存。在類加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存大小在類加載完成后便可完全確定。分配方式大致有兩種:指針碰撞和空閑列表。除了考慮如何劃分可用空間之外,還需要考慮在并發(fā)的情況下的線程安全。解決方案有兩種:一種是對(duì)分配空間的動(dòng)作進(jìn)行同步處理;另外一種本地線程分配緩沖(TLAB)。第三步,內(nèi)存空間初始化。如果使用TLAB,這一過(guò)程可以提前至TLAB分配時(shí)進(jìn)行。第四步,必要的設(shè)置。初始化后,虛擬機(jī)要對(duì)對(duì)

6、象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭中。上面的工作完成后,從虛擬機(jī)角度來(lái)看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了,但在程序員的角度來(lái)看,對(duì)象的創(chuàng)建才剛剛開(kāi)始,init方法還沒(méi)有執(zhí)行,所有字段都還為零。所以,一般來(lái)說(shuō),執(zhí)行new指令后會(huì)接著執(zhí)行init方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)可用的對(duì)象才算完全產(chǎn)生出來(lái)。三 對(duì)象的內(nèi)存布局在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局分為3塊:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。對(duì)象頭包括兩部分信息,一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼、GC分代年齡

7、、鎖狀態(tài)標(biāo)志、線程持有的鎖等;另一部分是類型指針,即對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,也是在程序代碼中所定義的各種類型字段內(nèi)容。無(wú)論是從父類繼承下來(lái),還是在子類中定義的,都需要記錄。這部分的存儲(chǔ)順序會(huì)受到虛擬機(jī)分配策略參數(shù)和字段在Java源碼中的順序的影響。對(duì)齊填充并不是必然存在的,也沒(méi)有特別的含義,它僅僅起著占位符的作用。由于HotspotVM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍。四 對(duì)象的訪問(wèn)定位建立對(duì)象是為了使用對(duì)象,我們的Java程序需要通過(guò)棧上的reference數(shù)據(jù)來(lái)操作堆上的具體對(duì)象

8、。由于reference類型在Java虛擬機(jī)規(guī)范里面只規(guī)定了一個(gè)指向?qū)ο蟮囊玫刂罚](méi)有定義這個(gè)引用應(yīng)該通過(guò)那種方式去定位,訪問(wèn)到Java堆中的對(duì)象位置,因此不同的虛擬機(jī)實(shí)現(xiàn)的訪問(wèn)方式可能不同,主流的方式有兩種:使用句柄和直接指針。句柄訪問(wèn)方式:Java堆中將劃分出一塊內(nèi)存來(lái)作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息。指針訪問(wèn)方式:reference變量中直接存儲(chǔ)的就是對(duì)象的地址,而Java堆對(duì)象一部分存儲(chǔ)了對(duì)象實(shí)例數(shù)據(jù),另外一部分存儲(chǔ)了到對(duì)象類型數(shù)據(jù)的指針。這兩種訪問(wèn)對(duì)象的方式各有優(yōu)勢(shì),使用句柄訪問(wèn)方式最大好處就是ref

9、erence中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象移動(dòng)時(shí)只需要改變句柄中的實(shí)例數(shù)據(jù)指針,而reference不需要改變。使用指針訪問(wèn)方式最大好處就是速度快,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷,就Hotspot虛擬機(jī)而言,它使用的是第二種方式(直接指針訪問(wèn))。五 JVM的內(nèi)存配置參數(shù)-XX:+<option> 啟用選項(xiàng) -XX:-<option> 不啟用選項(xiàng) -XX:<option>=<value> 將option參數(shù)的值設(shè)置為value堆設(shè)置 -Xms :初始堆大小 -Xmx :最大堆大小 -Xmn:新生代大小。通常為 Xmx 的 1/3 或 1/4。新生

10、代 = Eden + 2 個(gè) Survivor 空間。實(shí)際可用空間為 = Eden + 1 個(gè) Survivor,即 90%。-XX:NewSize=n :設(shè)置年輕代大小 -XX:NewRatio=n: 設(shè)置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個(gè)年輕代年老代和的1/4 -XX:SurvivorRatio=n :年輕代中Eden區(qū)與兩個(gè)Survivor區(qū)的比值。注意Survivor區(qū)有兩個(gè)。如:3,表示Eden:Survivor=3:2,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/5 -XX:PermSize=n 永久代(方法區(qū))的初始大小 -XX:MaxPer

11、mSize=n :設(shè)置永久代最大大小 -Xss 設(shè)定棧容量;對(duì)于HotSpot來(lái)說(shuō),雖然-Xoss參數(shù)(設(shè)置本地方法棧大小)存在,但實(shí)際上是無(wú)效的,因?yàn)樵贖otSpot中并不區(qū)分虛擬機(jī)和本地方法棧。 -XX:PretenureSizeThreshold (該設(shè)置只對(duì)Serial和ParNew收集器生效) 可以設(shè)置進(jìn)入老生代的大小限制 -XX:MaxTenuringThreshold=n(默認(rèn)15)垃圾最大年齡如果設(shè)置為0的話,則年輕代對(duì)象不經(jīng)過(guò)Survivor區(qū),直接進(jìn)入年老代。對(duì)于年老代比較多的應(yīng)用,可以提高效率。如果將此值設(shè)置為一個(gè)較大值,則年輕代對(duì)象會(huì)在Survivor區(qū)進(jìn)行多次復(fù)制,這

12、樣可以增加對(duì)象再年輕代的存活時(shí)間,增加在年輕代即被回收的概率 該參數(shù)只有在串行GC時(shí)才有效。收集器設(shè)置 -XX:+UseSerialGC :設(shè)置串行收集器 -XX:+UseParallelGC :設(shè)置并行收集器 -XX:+UseParallelOldGC :設(shè)置并行年老代收集器 -XX:+UseConcMarkSweepGC :設(shè)置并發(fā)收集器 垃圾回收統(tǒng)計(jì)信息 -XX:+PrintHeapAtGC 打印GC的heap詳情 -XX:+PrintGCDetails 打印GC詳情 -XX:+PrintGCTimeStamps 打印GC時(shí)間信息 -XX:+PrintTenuringDistributi

13、on 打印年齡信息等 -XX:+HandlePromotionFailure 老年代分配擔(dān)保(true or false) 并行收集器設(shè)置 -XX:ParallelGCThreads=n :設(shè)置并行收集器收集時(shí)使用的CPU數(shù)。并行收集線程數(shù)。 -XX:MaxGCPauseMillis=n :設(shè)置并行收集最大暫停時(shí)間 -XX:GCTimeRatio=n :設(shè)置垃圾回收時(shí)間占程序運(yùn)行時(shí)間的百分比。公式為1/(1+n) 并發(fā)收集器設(shè)置 -XX:+CMSIncrementalMode :設(shè)置為增量模式。適用于單CPU情況。 -XX:ParallelGCThreads=n :設(shè)置并發(fā)收集器年輕代收集方式

14、為并行收集時(shí),使用的CPU數(shù)。并行收集線程數(shù)。 其他 -XX:PermSize=10M和-XX:MaxPermSize=10M限制方法區(qū)大小。 -XX:MaxDirectMemorySize=10M指定DirectMemory(直接內(nèi)存)容量,如果不指定,則默認(rèn)與JAVA堆最大值(-Xmx指定)一樣。 -XX:+HeapDumpOnOutOfMemoryError 可以讓虛擬機(jī)在出現(xiàn)內(nèi)存溢出異常時(shí)Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲(chǔ)快照(.hprof文件)以便時(shí)候進(jìn)行分析(比如Eclipse Memory Analysis)。六 JVM的堆內(nèi)存(heap)簡(jiǎn)單的來(lái)說(shuō)Java的堆內(nèi)存分為兩塊:perman

15、t space(持久代/方法區(qū))和 heap space。持久代/方法區(qū):主要存儲(chǔ)結(jié)構(gòu)信息的地方,比如方法體,同時(shí)也是存儲(chǔ)靜態(tài)變量,以及靜態(tài)代碼塊的區(qū)域,構(gòu)造函數(shù),常量池,接口初始化等等 。與垃圾收集器要收集的Java對(duì)象關(guān)系不大。而heapspace分為新生代和年老代。新生代(由一個(gè)Eden區(qū)和倆個(gè)survivor區(qū)組成):對(duì)象被創(chuàng)建時(shí)(new)的對(duì)象通常被放在新生代的Eden區(qū)(除了一些占據(jù)內(nèi)存比較大的對(duì)象直接進(jìn)老年代),經(jīng)過(guò)一次GC收集后,存活下來(lái)的會(huì)被復(fù)制到survivor區(qū)(一個(gè)滿了,就全部移動(dòng)到另外一個(gè)大的中,但要保證其中一個(gè)survivor為空),經(jīng)過(guò)一定的Minor

16、GC(針對(duì)新生代的內(nèi)存回收)還活著的對(duì)象會(huì)被移動(dòng)到年老代(一些具體的移動(dòng)細(xì)節(jié)省略)。年老代:就是上述新生代移動(dòng)過(guò)來(lái)的和一些比較大的對(duì)象。FullGC是針對(duì)年老代的回收新生代的垃圾回收叫 Minor GC, 年老代的垃圾回收叫 Full GC。在年輕代中經(jīng)歷了多次垃圾回收后仍然存活的對(duì)象,就會(huì)被復(fù)制到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象。為了做到這點(diǎn),虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器。如果對(duì)象在Eden出生并經(jīng)過(guò)一次 Minor GC后仍然存活,并且能被Survivor容納的話,將被移動(dòng)到Survivor空間中,并且對(duì)象

17、年齡設(shè)為1。對(duì)象在Survivor空間中每熬過(guò)一次 Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲),就將會(huì)被晉升到年老代中。對(duì)象晉升年老代的年齡閾值,可以通過(guò)參數(shù)-XX:MaxTenuringThreshold設(shè)置。年老代溢出原因:循環(huán)上萬(wàn)次的字符串處理、創(chuàng)建上千萬(wàn)個(gè)對(duì)象、在一段代碼內(nèi)申請(qǐng)上百M(fèi)甚至上G的內(nèi)存。持久代溢出原因 :動(dòng)態(tài)加載了大量Java類而導(dǎo)致溢出。堆大小 = 新生代 + 老年代。其中,堆的大小可以通過(guò)參數(shù) Xms、-Xmx 來(lái)指定。 默認(rèn)的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通

18、過(guò)參數(shù) XX:NewRatio 來(lái)指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。其中,新生代 ( Young ) 被細(xì)分為 Eden 和 兩個(gè) Survivor 區(qū)域,這兩個(gè) Survivor 區(qū)域分別被命名為 from 和 to,以示區(qū)分。 默認(rèn)的,Edem : from : to = 8 : 1 : 1 ( 可以通過(guò)參數(shù) XX:SurvivorRatio 來(lái)設(shè)定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。 JVM 每次只會(huì)使用 Eden 和其中的一塊 Sur

19、vivor 區(qū)域來(lái)為對(duì)象服務(wù),所以無(wú)論什么時(shí)候,總是有一塊 Survivor 區(qū)域是空閑著的。 因此,新生代實(shí)際可用的內(nèi)存空間為 9/10 ( 即90% )的新生代空間。七 垃圾回收(GC)垃圾回收主要針對(duì)的是堆區(qū)的回收,因?yàn)闂^(qū)的內(nèi)存是隨著線程而釋放的。垃圾回收線程在jvm中優(yōu)先級(jí)相當(dāng)相當(dāng)?shù)汀?shù)組和對(duì)象在沒(méi)有引用變量指向它的時(shí)候,才變?yōu)槔荒茉俦皇褂茫匀徽紦?jù)內(nèi)存空間不放,在隨后的一個(gè)不確定的時(shí)間被垃圾回收器收走(釋放掉)。這也是 Java 比較占內(nèi)存的原因。在Java虛擬機(jī)一書(shū)中明確講了,釋放掉被占據(jù)的內(nèi)存空間是由GC完成,但是程序員無(wú)法明確強(qiáng)制其運(yùn)行,該空間在不被引用的時(shí)候不一定會(huì)

20、立即被釋放,這取決于GC本身,無(wú)法由程序員通過(guò)代碼控制。垃圾收集器(GC)程序開(kāi)發(fā)者只能建議JVM進(jìn)行回收,但何時(shí)回收,回收哪些,程序員不能控制。垃圾回收機(jī)制只是回收不再使用的內(nèi)存,如果程序有嚴(yán)重BUG,照樣內(nèi)存溢出。所以垃圾回收機(jī)制不能保證Java程序不會(huì)出現(xiàn)內(nèi)存溢出。死對(duì)象和活對(duì)象在垃圾回收器進(jìn)行垃圾回收前,第一件事情就是確定哪些對(duì)象還“存活”,哪些已經(jīng)“死去”。判斷對(duì)象是否還存活的算法主要是兩種:引用計(jì)數(shù)算法和可達(dá)性分析算法。引用計(jì)數(shù)算法。基本思想:給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。

21、這種算法實(shí)現(xiàn)簡(jiǎn)單,判斷效率也很高,但主流的Java虛擬機(jī)沒(méi)有選用引用計(jì)數(shù)算法來(lái)管理內(nèi)存,主要原因是它很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題。可達(dá)性分析算法。基本思想:通過(guò)一系列被稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí),則證明此對(duì)象是不可用。可當(dāng)做GC Roots的對(duì)象:虛擬機(jī)棧中引用的對(duì)象、方法區(qū)中類靜態(tài)屬性引用的對(duì)象、方法區(qū)中常量引用的對(duì)象、本地方法棧JNI引用的對(duì)象。無(wú)論是通過(guò)引用計(jì)數(shù)算法判斷對(duì)象的引用數(shù)量,還是通過(guò)可達(dá)性分析算法判斷對(duì)象引用鏈?zhǔn)欠窨蛇_(dá),判定對(duì)象是否存活都與“引用”有關(guān)。引用

22、關(guān)系:強(qiáng)引用>軟引用>弱引用>虛引用。垃圾收集算法“標(biāo)記-清除”(Mark-Sweep)算法。先標(biāo)記后清除。不足:一,效率問(wèn)題;二,空間問(wèn)題,產(chǎn)生大量不連續(xù)的內(nèi)存碎片。復(fù)制(Copying)算法。為了解決效率問(wèn)題,出現(xiàn)了復(fù)制算法。將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活的對(duì)象復(fù)制到另一塊上,然后再把已使用過(guò)的內(nèi)存空間一次清理掉。實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效,但代價(jià)就是把內(nèi)存縮小為原來(lái)的一半來(lái)使用。一般這種收集算法主要用來(lái)回收新生代,因?yàn)樾律膶?duì)象絕大多數(shù)是“朝生夕死”的,存活時(shí)間短,所以并不需要按1:1的比例來(lái)劃分空間,而是將內(nèi)存分為

23、一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中一塊Survivor空間。當(dāng)回收時(shí),將Eden和Survivor中還存活的對(duì)象一次性地復(fù)制到另一塊Survivor空間上,最后清理掉Eden空間和剛才用過(guò)的Survivor空間。Hotspot虛擬機(jī)默認(rèn)的Eden和Survivor的大小比例是8:1。“標(biāo)記-整理”(Mark-Compact)算法。在老年代中因?yàn)閷?duì)象存活率高、沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保,所以一般在老年代使用這種收集算法。分代收集算法。沒(méi)什么新的思想,只是上面兩種算法的在不同年代的使用。垃圾收集器1.Serial New/Serial Old Se

24、rial/Serial Old收集器是最基本最古老的收集器,它是一個(gè)單線程收集器,并且在它進(jìn)行垃圾收集時(shí),必須暫停所有用戶線程。Serial New收集器是針對(duì)新生代的收集器,采用的是Copying算法,Serial Old收集器是針對(duì)老年代的收集器,采用的是Mark-Compact算法。它的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單高效,但是缺點(diǎn)是會(huì)給用戶帶來(lái)停頓。2.Parallel NewParallel New收集器是Serial收集器的多線程版本(參照Serial New),使用多個(gè)線程進(jìn)行垃圾收集。除了Serial收集器外,目前只有Parallel New可以與CMS收集器配合工作。3.Parallel Sc

25、avenge Parallel Scavenge收集器是一個(gè)新生代的多線程收集器(并行收集器),它在回收期間不需要暫停其他用戶線程,其采用的是Copying算法,該收集器與前兩個(gè)收集器有所不同,它主要是為了達(dá)到一個(gè)可控的吞吐量。4.Parallel Old Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多線程和Mark-Compact算法。5.CMS CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,它是一種并發(fā)收集器,采用的是Mark-Sweep算法。CMS運(yùn)行的過(guò)程:初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)

26、記、并發(fā)清除。6.G1 G1收集器是當(dāng)今收集器技術(shù)發(fā)展最前沿的成果,它是一款面向服務(wù)端應(yīng)用的收集器,它能充分利用多CPU、多核環(huán)境。因此它是一款并行與并發(fā)收集器,并且它能建立可預(yù)測(cè)的停頓時(shí)間模型。G1的運(yùn)行過(guò)程:初始標(biāo)記、并發(fā)標(biāo)記、最終標(biāo)記、篩選回收。Hotspot虛擬機(jī)的垃圾收集器(兩個(gè)收集器之間的連線,說(shuō)明他們之間可以搭配使用)虛擬機(jī)執(zhí)行子系統(tǒng)一 類文件(Class文件)結(jié)構(gòu)實(shí)現(xiàn)語(yǔ)言與平臺(tái)無(wú)關(guān)的基礎(chǔ)是虛擬機(jī)和字節(jié)碼存儲(chǔ)格式。Class文件是一組以8位(一個(gè)字節(jié))為基礎(chǔ)單位的二進(jìn)制流,各數(shù)據(jù)項(xiàng)嚴(yán)格按順序排列其中,中間沒(méi)有添加任何分隔符。根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,CLASS文件格式采用一種

27、類似C語(yǔ)言結(jié)構(gòu)體的偽結(jié)構(gòu)來(lái)存儲(chǔ),這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表。無(wú)符號(hào)數(shù)屬于基本的數(shù)據(jù)類型,以u(píng)1,u2,u4,u8來(lái)分別表示一個(gè)字節(jié),兩個(gè)字節(jié),四個(gè)字節(jié)和8個(gè)字節(jié)的無(wú)符號(hào)數(shù),無(wú)符號(hào)數(shù)用來(lái)描述數(shù)字,索引引用,數(shù)量值或按照UTF8編碼構(gòu)成字符串?dāng)?shù)。表是由多個(gè)無(wú)符號(hào)數(shù)或其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型,所有表都習(xí)慣性的以"_info"結(jié)尾,表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù)。整個(gè)CLASS文件本質(zhì)上也是一張表。Class類文件格式按如下順序排列: 類型名稱數(shù)量u4magic(魔數(shù))1u2minor_version(次版本號(hào))1u2major_versi

28、on(主版本號(hào))1u2constant_pool_count(常量個(gè)數(shù))1 cp_infoconstant_pool(常量池表)constant_pool_count-1u2access_flags(類的訪問(wèn)控制權(quán)限)1u2this_class(類名)1u2super_class(父類名)1u2interfaces_count(接口個(gè)數(shù))1u2interfaces(接口名)interfaces_countu2fields_count(字段個(gè)數(shù))1field_infofields(字段表)fields_countu2 methods_count(方法的個(gè)數(shù))1method_i

29、nfomethods(方法表)methods_countu2attributes_count(屬性的個(gè)數(shù))1attribute_infoattributes(屬性表)attributes_count魔數(shù)(magic)(每個(gè)Class文件的頭4個(gè)字節(jié)):0xCAFEBABE。用來(lái)確定這個(gè)文件是否是Class文件,進(jìn)行身份識(shí)別。緊接著魔數(shù)的4個(gè)字節(jié)存儲(chǔ)的是Class文件的版本號(hào):第5和第6個(gè)字節(jié)是次版本號(hào)(2個(gè)字節(jié)),第7 和第8個(gè)字節(jié)是主版本號(hào)(2個(gè)字節(jié))。Java的版本號(hào)是從45(JDK 1.1)開(kāi)始,每個(gè)JDK版本發(fā)布主版本號(hào)加1,高版本號(hào)的JDK可以向下兼容以前版本的Class文件,但不能

30、運(yùn)行版本高于自己的CLASS文件。緊接著主次版本號(hào)之后的是常量池入口,常量池中常量的數(shù)量不同,用常量池計(jì)數(shù)器(2個(gè)字節(jié))代表常量池容量的計(jì)數(shù)值。計(jì)數(shù)器從1而不是0開(kāi)始,例如當(dāng)常量池容量為0x0016,十進(jìn)制為22,代表有21個(gè)常量。索引為121。沒(méi)有使用0索引是因?yàn)樵诤竺婺承┲赶虺A砍氐乃饕梢酝ㄟ^(guò)0索引表示不引用任何一個(gè)常量池項(xiàng)目的意思。常量池中主要存放兩大類常量:字面量(Literal)和符號(hào)引用(Symbolic References)。字面量的例子有文本字符串,被聲明為final的常量值等。符號(hào)引用包含三類常量:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。常量池后面緊接

31、著是類的訪問(wèn)權(quán)限控制符,類以及父類的全限定名,以及接口的個(gè)數(shù),之后是接口的全限定名,全限定名都是指向常量池的符號(hào)引用。再下面就是字段表集合、方法表集合和屬性表集合。二 虛擬機(jī)類加載機(jī)制虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制。在Java語(yǔ)言里,類型的加載、連接(驗(yàn)證、準(zhǔn)備、解析)和初始化的過(guò)程都是在程序運(yùn)行期間完成的。一個(gè)類的生命周期包括:加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載7個(gè)階段。部分解析可以在初始化開(kāi)始之后再開(kāi)始,這樣可以支持java的運(yùn)行時(shí)綁定。Java虛擬機(jī)規(guī)范中并沒(méi)

32、有強(qiáng)制規(guī)定什么情況下需要開(kāi)始類加載過(guò)程的第一個(gè)階段:加載,這個(gè)交給虛擬機(jī)自由把握,但卻嚴(yán)格規(guī)定了有且只有5種情況必須立即對(duì)類進(jìn)行初始化:1)遇到new創(chuàng)建實(shí)例,getstatic獲取類的靜態(tài)字段,putstatic設(shè)置類的靜態(tài)字段,invokestatic調(diào)用類的靜態(tài)方法2)用java.lang.reflect包方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果這個(gè)類沒(méi)有初始化過(guò),那么先觸發(fā)其初始化3)初始化一個(gè)類的時(shí)候,如果父類沒(méi)有進(jìn)行初始化,那么必須先觸發(fā)其父類的初始化4)當(dāng)虛擬機(jī)啟動(dòng)的時(shí)候,需要指定一個(gè)執(zhí)行的主類,虛擬機(jī)會(huì)先初始化這個(gè)主類5)當(dāng)使用JDK 1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang

33、.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行初始化,則需要先觸發(fā)其初始化。這5中場(chǎng)景中的行為稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用。除此之外,所有引用類的方法都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用。例如:通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類的初始化;用new關(guān)鍵字創(chuàng)建數(shù)組,通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)相應(yīng)的類初始化:AClass a=new AClass10;調(diào)用一個(gè)類的靜態(tài)常量也不會(huì)觸發(fā)該類的初始化,因?yàn)檎{(diào)用類在編譯階段就已經(jīng)把常量轉(zhuǎn)化為對(duì)自己的常量池的引用。接

34、口的初始化過(guò)程與類的初始化過(guò)程的顯著區(qū)別:當(dāng)一個(gè)類初始化時(shí),要求其父類全部都已初始化過(guò)了,但是一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成了初始化,只要在真正使用到父接口的時(shí)候(如引用父接口中定義的常量)才會(huì)初始化。三 類加載的過(guò)程類加載的過(guò)程:加載、驗(yàn)證、準(zhǔn)備、解析和初始化。加載階段是整個(gè)類加載階段的第一個(gè)階段,在加載階段主要完成3件事情:1)通過(guò)類的全限定名來(lái)回去定義此類的二進(jìn)制流2)將這個(gè)二進(jìn)制流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)3)在java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問(wèn)入口。驗(yàn)證階段,目的是為了確保Class文件的

35、字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。驗(yàn)證階段大致上完成以下4種驗(yàn)證:Class文件格式的驗(yàn)證,元數(shù)據(jù)的驗(yàn)證,字節(jié)碼的驗(yàn)證,符號(hào)引用驗(yàn)證。1)Class文件格式驗(yàn)證為了驗(yàn)證是否符合Class文件的格式,并且能被當(dāng)前版本的虛擬機(jī)處理。這個(gè)階段是驗(yàn)證是基于二進(jìn)制字節(jié)流進(jìn)行的,只有通過(guò)了這個(gè)階段的驗(yàn)證后,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲(chǔ),所以后面的3個(gè)驗(yàn)證階段全部是基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的,不會(huì)再直接操作字節(jié)流;2)元數(shù)據(jù)驗(yàn)證是為了對(duì)類的元數(shù)據(jù)信息進(jìn)行語(yǔ)義校驗(yàn),保證不存在不符合java語(yǔ)義規(guī)范的元數(shù)據(jù)信息;3)字節(jié)碼驗(yàn)證主要是通過(guò)數(shù)據(jù)流和控制流,對(duì)類的方法體中的

36、字節(jié)碼進(jìn)行校驗(yàn)分析;4)符號(hào)引用驗(yàn)證主要是為了給解析階段符號(hào)引用轉(zhuǎn)化為直接引用做準(zhǔn)備,對(duì)類自身以外的信息(常量池中的各種符號(hào)引用)進(jìn)行匹配性校驗(yàn)。準(zhǔn)備階段,正式為類變量(被static修飾的變量)分配內(nèi)存并設(shè)置初始值。這些變量使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。說(shuō)明兩點(diǎn):這個(gè)階段僅對(duì)類變量分配內(nèi)存,不對(duì)實(shí)例變量分配,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中;這里的初始值“通常情況”下是數(shù)據(jù)類型的零值。“通常情況”:public static int a = 123;/類變量在準(zhǔn)備階段初始化的值為0,而在初始化階段,在<cinit>構(gòu)造方法中會(huì)把a(bǔ)的值初始化為123。“

37、不通常情況”:public static final int a = 123;/用final修飾的類變量在準(zhǔn)備階段,會(huì)把a(bǔ)的值初始化為123。解析階段,把虛擬機(jī)在常量池中的符號(hào)引用替換為直接引用的過(guò)程。解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用(后面3種是與JDK 1.7新增的動(dòng)態(tài)語(yǔ)言支持有關(guān))。初始化階段,是執(zhí)行類構(gòu)造器<cinit>()方法的過(guò)程,<cinit>()會(huì)自動(dòng)收集類中的所有類變量以及靜態(tài)語(yǔ)句塊(static),在初始化<cinit>()方法的時(shí)候,虛擬機(jī)會(huì)自動(dòng)調(diào)用父類的<cinit&g

38、t;()方法,接口的<cinit>()方法可以到使用的時(shí)候在去初始化,虛擬機(jī)會(huì)保證<cinit>()方法在多線程環(huán)境先被正確的加鎖和同步。還有一個(gè)<init>()方法,這個(gè)方法是實(shí)例構(gòu)造器(類的構(gòu)造函數(shù)),在創(chuàng)建實(shí)例的時(shí)候會(huì)被調(diào)用并且初始化。四 Java類加載器(Java ClassLoader)對(duì)于任意一個(gè)類,都需要根據(jù)加載它的類加載器和這個(gè)類本身一同確定其在java虛擬機(jī)中的唯一性。每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間。通俗講,判定兩個(gè)類是否相等時(shí),不僅要判斷兩個(gè)類名是否相同,而且要判斷是否由同一個(gè)類加載器加載的。從Java虛擬機(jī)角度來(lái)講,只存在兩

39、種不同的加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器使用C+語(yǔ)言實(shí)現(xiàn),是虛擬機(jī)的一部分;另一種就是所有其他的類加載器,這些類加載器都由Java語(yǔ)言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全部繼承自抽象類java.lang.ClassLoader。從Java開(kāi)發(fā)人員的角度來(lái)看,類加載器還可以劃分得更細(xì)致一些,絕大部分Java程序都會(huì)使用一下3種系統(tǒng)提供的類加載器:?jiǎn)?dòng)類加載器(Bootstrap ClassLoader)、擴(kuò)展類加載器(Extension ClassLoader)、應(yīng)用程序類加載器(App ClassLoader)。1)Bootstrap Class

40、Loader(引導(dǎo)類加載器)負(fù)責(zé)把存放在$JAVA_HOME中jre/lib目錄中的核心庫(kù)和基礎(chǔ)庫(kù)加載到虛擬機(jī)內(nèi)存中。由C+實(shí)現(xiàn),不是ClassLoader子類,無(wú)法被Java程序直接引用。2)Extension ClassLoader(擴(kuò)展類加載器)負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext或-Djava.ext.dirs指定目錄下所有類庫(kù)。開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器。3)AppClassLoade(應(yīng)用程序類加載器)負(fù)責(zé)加載用戶類路徑classpath中指定的類庫(kù)。開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器。4)User ClassLoader(

41、自定義類加載器)屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader。類加載器雙親委派模型類加載器使用雙親委派模型,這樣當(dāng)要加載一個(gè)類時(shí),首先查找這個(gè)類是否已經(jīng)被加載過(guò),如果沒(méi)有,那么類加載器會(huì)把這個(gè)類委派給這個(gè)加載器的父類去進(jìn)行加載,如果父類不能加載,那么再自己嘗試加載。加載過(guò)程中會(huì)先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrapClassLoader逐層檢查,只要某個(gè)classloader已加載過(guò)就視為已加載此類,保證此類只被所有ClassLoader加載一次

42、。而加載的順序是自頂向下,也就是由上層來(lái)逐層嘗試加載此類。ClassLoader加載類用的是全盤(pán)負(fù)責(zé)委托機(jī)制。所謂全盤(pán)負(fù)責(zé),即是當(dāng)一個(gè)classloader加載一個(gè)Class的時(shí)候,這個(gè)Class所依賴的和引用的所有 Class也由這個(gè)classloader負(fù)責(zé)載入,除非是顯式的使用另外一個(gè)classloader載入。所以,當(dāng)我們自定義的classloader加載成功了pany.MyClass以后,MyClass里所有依賴的class都由這個(gè)classLoader來(lái)加載完成。五 字節(jié)碼執(zhí)行棧幀,是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的虛擬機(jī)棧的棧元素。棧幀中從上

43、到下依次存儲(chǔ)了方法的局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接和方法返回地址等信息。每個(gè)方法從調(diào)用開(kāi)始到調(diào)用結(jié)束,都對(duì)應(yīng)著一個(gè)棧幀從入棧到出棧的過(guò)程。一個(gè)棧幀需要分配多大的內(nèi)存,在編譯程序代碼期間就確定了。一個(gè)線程中只有棧頂?shù)臈攀怯行У模Q為當(dāng)前棧幀,這個(gè)棧幀所關(guān)聯(lián)的方法就是當(dāng)前方法。局部變量表,是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。局部變量表的容量以變量槽(Slot)為最小單位。為了節(jié)省棧幀空間,Slot是可以重用,但可能會(huì)影響到系統(tǒng)的垃圾收集行為。在方法內(nèi)的局部變量定義了但沒(méi)有賦初始值是不能使用的。一般編譯器會(huì)檢查到并提示這一點(diǎn)。操作數(shù)棧,也稱為操作棧,是一個(gè)后入先出(L

44、IFO)棧。操作數(shù)棧中元素的數(shù)據(jù)類型必須要與字節(jié)碼指令的序列完全一致。動(dòng)態(tài)鏈接,是在運(yùn)行期間把符號(hào)引用轉(zhuǎn)化為直接引用的過(guò)程,相對(duì)于靜態(tài)解析(在類加載過(guò)程的解析階段把符號(hào)引用轉(zhuǎn)化為直接引用)。方法的返回地址,方法返回有兩種類型,一種是正常完成出口,另一種是異常完成出口,方法退出的過(guò)程等同于棧幀出棧,因此棧幀出棧的時(shí)候可能執(zhí)行的操作有:恢復(fù)上層調(diào)用方法的局部變量表和操作數(shù)棧,把返回值壓入調(diào)用方法的操作數(shù)棧中,調(diào)整PC計(jì)數(shù)器的值以執(zhí)行方法調(diào)用指令的后一條指令等。方法調(diào)用,不等同方法執(zhí)行,唯一的任務(wù)就是確定被調(diào)用方法的版本,不涉及方法內(nèi)部的具體運(yùn)行過(guò)程。Class文件的編譯過(guò)程中不包含傳統(tǒng)編譯中的連接

45、步驟,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存入口地址(相當(dāng)于之前說(shuō)的直接引用)。靜態(tài)方法、實(shí)例構(gòu)造器、私有方法、父類方法和final方法,這些方法叫做非虛方法。這類非虛方法的調(diào)用叫做解析。解析調(diào)用一定是個(gè)靜態(tài)的過(guò)程,在編譯期間就完全確定,在類加載的解析階段就會(huì)把涉及的符號(hào)引用全部轉(zhuǎn)變?yōu)榭纱_定的直接引用,不會(huì)延遲到運(yùn)行期間再去完成。Human man = new Man(); /Human是變量的靜態(tài)類型 Man是變量的實(shí)際類型靜態(tài)分派:所有依賴靜態(tài)類型來(lái)定位方法執(zhí)行版本的分派動(dòng)作都稱為靜態(tài)分派,靜態(tài)分派的典型應(yīng)用就是方法重載。靜態(tài)分派發(fā)生在編譯階段,因

46、此確定靜態(tài)分派的動(dòng)作實(shí)際上不是由虛擬機(jī)來(lái)執(zhí)行的。動(dòng)態(tài)分派:在運(yùn)行期根據(jù)實(shí)際類型確定方法的執(zhí)行版本的分派過(guò)程稱為動(dòng)態(tài)分派,動(dòng)態(tài)分派的典型應(yīng)用就是方法重寫(xiě)。宗量:方法的接受者和方法的參數(shù)統(tǒng)稱為方法的宗量。單分派:根據(jù)一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇多分派:根據(jù)多個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇Java是一種靜態(tài)多分派,動(dòng)態(tài)單分派語(yǔ)言。類的方法區(qū)會(huì)保存一張?zhí)摲椒ū恚娣欧椒ǖ膶?shí)際入口地址,如果沒(méi)有重寫(xiě)父類的方法,那么入口與父類的一樣,如果重寫(xiě)了父類的方法,那么方法的入口地址指向自己的方法入口地址。方法表一般在類加載的連接階段進(jìn)行初始化,準(zhǔn)備了類變量的初始值之后,虛擬機(jī)會(huì)把該類的方法表也初始化完畢,這是java實(shí)現(xiàn)

47、動(dòng)態(tài)分派方法。方法執(zhí)行,Java虛擬機(jī)的執(zhí)行引擎在執(zhí)行Java代碼的時(shí)候都有解釋執(zhí)行(通過(guò)解釋器執(zhí)行)和編譯執(zhí)行(通過(guò)即時(shí)編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇。基于棧的解釋器執(zhí)行在Class文件格式與執(zhí)行引擎這部分中,用戶的程序能直接影響的內(nèi)容并不太多,Class文件以何種格式存儲(chǔ),類型何時(shí)加載,如何連接,以及虛擬機(jī)如何執(zhí)行字節(jié)碼指令等都是由虛擬機(jī)直接控制的行為,用戶程序無(wú)法進(jìn)行干預(yù)。能通過(guò)程序進(jìn)行操作的,主要是字節(jié)碼生成和類加載器這兩部分。程序編譯一 編譯期Java語(yǔ)言的“編譯期”其實(shí)是一段不確定的操作過(guò)程,因?yàn)樗赡苁侵敢粋€(gè)前端編譯器把*.java文件轉(zhuǎn)變成*.class文件的過(guò)程;也可能是指

48、虛擬機(jī)的后端運(yùn)行期編譯器把字節(jié)碼轉(zhuǎn)變成機(jī)器碼的過(guò)程。Java的即時(shí)編譯器在運(yùn)行期的優(yōu)化過(guò)程對(duì)于程序運(yùn)行來(lái)說(shuō)更重要,而前端編譯器在編譯期的優(yōu)化過(guò)程對(duì)于程序編碼來(lái)說(shuō)關(guān)系更加密切。Javac編譯器,是Sun公司的前端編譯期,它本身就是一個(gè)由Java語(yǔ)言編寫(xiě)的程序。從Javac的代碼來(lái)看,編譯過(guò)程大致可以分為3個(gè)過(guò)程:解析與填充符號(hào)表過(guò)程、插入式注解處理器的注解處理過(guò)程和分析與字節(jié)碼生成過(guò)程。Javac的編譯過(guò)程過(guò)程1.1:解析(詞法、語(yǔ)法分析)過(guò)程1.2:填充到符號(hào)表過(guò)程2:執(zhí)行注解處理過(guò)程3.1:標(biāo)注檢查過(guò)程3.2:數(shù)據(jù)及控制流分析過(guò)程3.3:解語(yǔ)法糖(常見(jiàn)語(yǔ)法糖:泛型、自動(dòng)裝箱/拆箱、變長(zhǎng)參數(shù)等

49、)過(guò)程3.4:字節(jié)碼生成二 運(yùn)行期前面Javac這類將Java源代碼轉(zhuǎn)變成字節(jié)碼的編譯器一般稱為“前端編譯器”,是因?yàn)樗煌瓿闪藦某绦虻街虚g字節(jié)碼的生成,而在此之后,還有一組在虛擬機(jī)內(nèi)部的“后端編譯器”完成了從字節(jié)碼生成機(jī)器碼的過(guò)程,這類編譯器一般稱作為即時(shí)編譯器或JIT編譯器,工作在程序的運(yùn)行期。這類編譯器的編譯速度及編譯結(jié)果的優(yōu)劣,是衡量一個(gè)虛擬機(jī)性能很重要的指標(biāo)。Java程序最初是通過(guò)解釋器進(jìn)行解釋執(zhí)行的,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼的運(yùn)行特別頻繁時(shí),就會(huì)把這些代碼認(rèn)定為“熱點(diǎn)代碼”。為了提高熱點(diǎn)代碼的執(zhí)行效率,在運(yùn)行時(shí),虛擬機(jī)將會(huì)把這些熱點(diǎn)代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼,并進(jìn)行各層次優(yōu)

50、化,完成這個(gè)任務(wù)的編譯器就是即時(shí)編譯器。(C/C+:靜態(tài)優(yōu)化編譯器)Hotspot虛擬機(jī)采用的是解釋器與編譯器并存的架構(gòu)。解釋器和編譯器兩者各有優(yōu)勢(shì):當(dāng)程序需要快速啟動(dòng)和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用,省去編譯時(shí)間,立即執(zhí)行。在程序運(yùn)行后,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用,把越來(lái)越多的代碼編譯成本地代碼后,可以獲取更高的執(zhí)行效率。Hotspot虛擬機(jī)中內(nèi)置了兩個(gè)即時(shí)編譯器,分別稱為Client Compiler 和Server Compiler(簡(jiǎn)稱C1和C2)。用C1可以獲取更高的編譯速度,用C2可以獲取更好的編譯質(zhì)量。程序使用哪個(gè)編譯器,取決虛擬機(jī)運(yùn)行模式,虛擬機(jī)會(huì)根據(jù)自身版本和宿主

51、機(jī)器的硬件性能自動(dòng)選擇運(yùn)行模式,用戶也可以自己使用參數(shù)強(qiáng)制指定運(yùn)行模式。在運(yùn)行過(guò)程中能被即時(shí)編譯器編譯的“熱點(diǎn)代碼”有兩類:被多次調(diào)用的方法;被多次執(zhí)行的循環(huán)體。判斷一段代碼是不是熱點(diǎn)代碼,需不需要觸發(fā)即時(shí)編譯,這樣的行為稱為熱點(diǎn)探測(cè)。主要的熱點(diǎn)探測(cè)判定方式:基于采樣的熱點(diǎn)探測(cè)和基于計(jì)數(shù)器的熱點(diǎn)探測(cè)(Hotspot)。基于計(jì)數(shù)器的熱點(diǎn)探測(cè)方式為每個(gè)方法準(zhǔn)備了兩個(gè)計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(Client模式1500次,Server模式10000次,計(jì)數(shù)熱度衰減)和回邊計(jì)數(shù)器(Client模式13995次,Server模式10700次,沒(méi)有計(jì)數(shù)熱度衰減)。編譯優(yōu)化技術(shù):公共子表達(dá)式消除、數(shù)組范圍檢查消

52、除、方法內(nèi)聯(lián)、逃逸分析Javac字節(jié)碼的編譯器與虛擬機(jī)內(nèi)的JIT編譯器的執(zhí)行過(guò)程合并起來(lái)其實(shí)就等同于一個(gè)傳統(tǒng)編譯器所執(zhí)行的編譯過(guò)程。高效并發(fā)一 Java內(nèi)存模型Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問(wèn)規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣底層細(xì)節(jié)。此處的變量與Java編程時(shí)所說(shuō)的變量不一樣,指包括了實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但是不包括局部變量與方法參數(shù),后者是線程私有的,不會(huì)被共享。Java內(nèi)存模型中規(guī)定了:所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程使用到的變量到主內(nèi)存副本拷貝,線程對(duì)變量的所有操作(讀取、

53、賦值)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存中的變量。不同線程之間無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要在主內(nèi)存來(lái)完成。線程、主內(nèi)存和工作內(nèi)存的交互關(guān)系如下圖所示。這里的主內(nèi)存、工作內(nèi)存與Java內(nèi)存區(qū)域的Java堆、棧、方法區(qū)不是同一層次內(nèi)存劃分。如果非要對(duì)應(yīng)起來(lái),主內(nèi)存主要對(duì)應(yīng)于Java堆中的對(duì)象實(shí)例數(shù)據(jù)部分,而工作內(nèi)存則對(duì)應(yīng)于虛擬機(jī)棧中部分區(qū)域。二 內(nèi)存間交互操作關(guān)于主內(nèi)存與工作內(nèi)存之間的具體交互協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步到主內(nèi)存之間的實(shí)現(xiàn)細(xì)節(jié),Java內(nèi)存模型定義了以下八種操作來(lái)完成:· lock(鎖定):作用于主

54、內(nèi)存的變量,把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占狀態(tài)。· unlock(解鎖):作用于主內(nèi)存變量,把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定。· read(讀取):作用于主內(nèi)存變量,把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用· load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。· use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。· assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。· store(存儲(chǔ)):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write的操作。· write(寫(xiě)入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存的變量中。如果要把一個(gè)變量從主內(nèi)存中復(fù)制到工作內(nèi)存,就需

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論