




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1、JVM的自動內(nèi)存管理機(jī)制一 如何劃分JVM內(nèi)存JVM所管理的內(nèi)存在運(yùn)行時(shí)會被分為這樣幾個(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ú)立存儲,是線程隔離的。程序計(jì)數(shù)器所在的內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。虛擬機(jī)棧,線程私有,它的生命周期與線程相同。虛擬機(jī)棧區(qū)描述的是Java方法執(zhí)行內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會創(chuàng)建一個(gè)棧幀用于存儲局部變量、操作數(shù)棧、動態(tài)鏈接、方法出
2、口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。局部變量表存放了8種基本數(shù)據(jù)類型、對象的引用和returnAddress。局部變量表所需的內(nèi)存空間在編譯期間完成分配,在方法運(yùn)行期間不會改變局部變量表的大小。本地方法棧,作用與虛擬機(jī)棧區(qū)是相似的,他們之間的區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。堆,Java堆,也稱GC堆,是最大的一塊,是被線程共享的區(qū)域,在虛擬機(jī)啟動時(shí)創(chuàng)建。所有類的實(shí)例(對象)和數(shù)組都是在堆上分配內(nèi)存的,堆內(nèi)存由存活和死亡的對象,空閑碎片區(qū)組成,對象所占的堆內(nèi)存是由自動內(nèi)存管
3、理系統(tǒng)回收。(數(shù)組是一種對象)從內(nèi)存回收角度來看,Java堆還可以細(xì)分為新生代和老年代;甚至還可以分為Eden空間、 From Survivor空間、To Survivor空間等。從內(nèi)存分配角度來看,線程共享的Java堆中可能劃分出多個(gè)線程私有的分配緩沖區(qū)(TLAB)。Java堆可以處于物理上不連續(xù)的內(nèi)存中,只要邏輯上連續(xù)即可。方法區(qū)在JVM中也是一個(gè)非常重要的區(qū)域,在HotSpot虛擬機(jī)上,方法區(qū)被稱為“永久代”。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆區(qū)的一個(gè)邏輯部分,但還是要區(qū)分來對待。方法區(qū)用于存儲已被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)存回收只要針對常量池的回收和對類型的卸載。運(yùn)行時(shí)常量池,是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯期生成的各種字面量和符號引用。二 對象的創(chuàng)建在語言層面上,創(chuàng)建對象通常僅僅是一個(gè)new關(guān)鍵字而已。但在虛擬機(jī)中,對象的創(chuàng)建過程大致分為以下四步:第一步,檢查類加載。虛擬機(jī)遇到一條new指令時(shí),首
5、先需要去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號引用,并且檢查這個(gè)符號引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。第二步,分配內(nèi)存。在類加載檢查通過后,接下來虛擬機(jī)將為新生對象分配內(nèi)存。對象所需的內(nèi)存大小在類加載完成后便可完全確定。分配方式大致有兩種:指針碰撞和空閑列表。除了考慮如何劃分可用空間之外,還需要考慮在并發(fā)的情況下的線程安全。解決方案有兩種:一種是對分配空間的動作進(jìn)行同步處理;另外一種本地線程分配緩沖(TLAB)。第三步,內(nèi)存空間初始化。如果使用TLAB,這一過程可以提前至TLAB分配時(shí)進(jìn)行。第四步,必要的設(shè)置。初始化后,虛擬機(jī)要對對
6、象進(jìn)行必要的設(shè)置,例如這個(gè)對象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭中。上面的工作完成后,從虛擬機(jī)角度來看,一個(gè)新的對象已經(jīng)產(chǎn)生了,但在程序員的角度來看,對象的創(chuàng)建才剛剛開始,init方法還沒有執(zhí)行,所有字段都還為零。所以,一般來說,執(zhí)行new指令后會接著執(zhí)行init方法,把對象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)可用的對象才算完全產(chǎn)生出來。三 對象的內(nèi)存布局在HotSpot虛擬機(jī)中,對象在內(nèi)存中存儲的布局分為3塊:對象頭、實(shí)例數(shù)據(jù)和對齊填充。對象頭包括兩部分信息,一部分用于存儲對象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼、GC分代年齡
7、、鎖狀態(tài)標(biāo)志、線程持有的鎖等;另一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對象是哪個(gè)類的實(shí)例。實(shí)例數(shù)據(jù)部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型字段內(nèi)容。無論是從父類繼承下來,還是在子類中定義的,都需要記錄。這部分的存儲順序會受到虛擬機(jī)分配策略參數(shù)和字段在Java源碼中的順序的影響。對齊填充并不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。由于HotspotVM的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍。四 對象的訪問定位建立對象是為了使用對象,我們的Java程序需要通過棧上的reference數(shù)據(jù)來操作堆上的具體對象
8、。由于reference類型在Java虛擬機(jī)規(guī)范里面只規(guī)定了一個(gè)指向?qū)ο蟮囊玫刂?,并沒有定義這個(gè)引用應(yīng)該通過那種方式去定位,訪問到Java堆中的對象位置,因此不同的虛擬機(jī)實(shí)現(xiàn)的訪問方式可能不同,主流的方式有兩種:使用句柄和直接指針。句柄訪問方式:Java堆中將劃分出一塊內(nèi)存來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息。指針訪問方式:reference變量中直接存儲的就是對象的地址,而Java堆對象一部分存儲了對象實(shí)例數(shù)據(jù),另外一部分存儲了到對象類型數(shù)據(jù)的指針。這兩種訪問對象的方式各有優(yōu)勢,使用句柄訪問方式最大好處就是ref
9、erence中存儲的是穩(wěn)定的句柄地址,在對象移動時(shí)只需要改變句柄中的實(shí)例數(shù)據(jù)指針,而reference不需要改變。使用指針訪問方式最大好處就是速度快,它節(jié)省了一次指針定位的時(shí)間開銷,就Hotspot虛擬機(jī)而言,它使用的是第二種方式(直接指針訪問)。五 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è)定棧容量;對于HotSpot來說,雖然-Xoss參數(shù)(設(shè)置本地方法棧大?。┐嬖冢珜?shí)際上是無效的,因?yàn)樵贖otSpot中并不區(qū)分虛擬機(jī)和本地方法棧。 -XX:PretenureSizeThreshold (該設(shè)置只對Serial和ParNew收集器生效) 可以設(shè)置進(jìn)入老生代的大小限制 -XX:MaxTenuringThreshold=n(默認(rèn)15)垃圾最大年齡如果設(shè)置為0的話,則年輕代對象不經(jīng)過Survivor區(qū),直接進(jìn)入年老代。對于年老代比較多的應(yīng)用,可以提高效率。如果將此值設(shè)置為一個(gè)較大值,則年輕代對象會在Survivor區(qū)進(jìn)行多次復(fù)制,這
12、樣可以增加對象再年輕代的存活時(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)儲快照(.hprof文件)以便時(shí)候進(jìn)行分析(比如Eclipse Memory Analysis)。六 JVM的堆內(nèi)存(heap)簡單的來說Java的堆內(nèi)存分為兩塊:perman
15、t space(持久代/方法區(qū))和 heap space。持久代/方法區(qū):主要存儲結(jié)構(gòu)信息的地方,比如方法體,同時(shí)也是存儲靜態(tài)變量,以及靜態(tài)代碼塊的區(qū)域,構(gòu)造函數(shù),常量池,接口初始化等等 。與垃圾收集器要收集的Java對象關(guān)系不大。而heapspace分為新生代和年老代。新生代(由一個(gè)Eden區(qū)和倆個(gè)survivor區(qū)組成):對象被創(chuàng)建時(shí)(new)的對象通常被放在新生代的Eden區(qū)(除了一些占據(jù)內(nèi)存比較大的對象直接進(jìn)老年代),經(jīng)過一次GC收集后,存活下來的會被復(fù)制到survivor區(qū)(一個(gè)滿了,就全部移動到另外一個(gè)大的中,但要保證其中一個(gè)survivor為空),經(jīng)過一定的Minor
16、GC(針對新生代的內(nèi)存回收)還活著的對象會被移動到年老代(一些具體的移動細(xì)節(jié)省略)。年老代:就是上述新生代移動過來的和一些比較大的對象。FullGC是針對年老代的回收新生代的垃圾回收叫 Minor GC, 年老代的垃圾回收叫 Full GC。在年輕代中經(jīng)歷了多次垃圾回收后仍然存活的對象,就會被復(fù)制到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長的對象。為了做到這點(diǎn),虛擬機(jī)給每個(gè)對象定義了一個(gè)對象年齡計(jì)數(shù)器。如果對象在Eden出生并經(jīng)過一次 Minor GC后仍然存活,并且能被Survivor容納的話,將被移動到Survivor空間中,并且對象
17、年齡設(shè)為1。對象在Survivor空間中每熬過一次 Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲),就將會被晉升到年老代中。對象晉升年老代的年齡閾值,可以通過參數(shù)-XX:MaxTenuringThreshold設(shè)置。年老代溢出原因:循環(huán)上萬次的字符串處理、創(chuàng)建上千萬個(gè)對象、在一段代碼內(nèi)申請上百M(fèi)甚至上G的內(nèi)存。持久代溢出原因 :動態(tài)加載了大量Java類而導(dǎo)致溢出。堆大小 = 新生代 + 老年代。其中,堆的大小可以通過參數(shù) Xms、-Xmx 來指定。 默認(rèn)的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通
18、過參數(shù) XX:NewRatio 來指定 ),即:新生代 ( 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 ( 可以通過參數(shù) XX:SurvivorRatio 來設(shè)定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。 JVM 每次只會使用 Eden 和其中的一塊 Sur
19、vivor 區(qū)域來為對象服務(wù),所以無論什么時(shí)候,總是有一塊 Survivor 區(qū)域是空閑著的。 因此,新生代實(shí)際可用的內(nèi)存空間為 9/10 ( 即90% )的新生代空間。七 垃圾回收(GC)垃圾回收主要針對的是堆區(qū)的回收,因?yàn)闂^(qū)的內(nèi)存是隨著線程而釋放的。垃圾回收線程在jvm中優(yōu)先級相當(dāng)相當(dāng)?shù)?。?shù)組和對象在沒有引用變量指向它的時(shí)候,才變?yōu)槔?,不能再被使用,但仍然占?jù)內(nèi)存空間不放,在隨后的一個(gè)不確定的時(shí)間被垃圾回收器收走(釋放掉)。這也是 Java 比較占內(nèi)存的原因。在Java虛擬機(jī)一書中明確講了,釋放掉被占據(jù)的內(nèi)存空間是由GC完成,但是程序員無法明確強(qiáng)制其運(yùn)行,該空間在不被引用的時(shí)候不一定會
20、立即被釋放,這取決于GC本身,無法由程序員通過代碼控制。垃圾收集器(GC)程序開發(fā)者只能建議JVM進(jìn)行回收,但何時(shí)回收,回收哪些,程序員不能控制。垃圾回收機(jī)制只是回收不再使用的內(nèi)存,如果程序有嚴(yán)重BUG,照樣內(nèi)存溢出。所以垃圾回收機(jī)制不能保證Java程序不會出現(xiàn)內(nèi)存溢出。死對象和活對象在垃圾回收器進(jìn)行垃圾回收前,第一件事情就是確定哪些對象還“存活”,哪些已經(jīng)“死去”。判斷對象是否還存活的算法主要是兩種:引用計(jì)數(shù)算法和可達(dá)性分析算法。引用計(jì)數(shù)算法?;舅枷耄航o對象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對象就是不可能再被使用的。
21、這種算法實(shí)現(xiàn)簡單,判斷效率也很高,但主流的Java虛擬機(jī)沒有選用引用計(jì)數(shù)算法來管理內(nèi)存,主要原因是它很難解決對象之間相互循環(huán)引用的問題??蛇_(dá)性分析算法?;舅枷耄和ㄟ^一系列被稱為“GC Roots”的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個(gè)對象到GC Roots沒有任何引用鏈相連時(shí),則證明此對象是不可用??僧?dāng)做GC Roots的對象:虛擬機(jī)棧中引用的對象、方法區(qū)中類靜態(tài)屬性引用的對象、方法區(qū)中常量引用的對象、本地方法棧JNI引用的對象。無論是通過引用計(jì)數(shù)算法判斷對象的引用數(shù)量,還是通過可達(dá)性分析算法判斷對象引用鏈?zhǔn)欠窨蛇_(dá),判定對象是否存活都與“引用”有關(guān)。引用
22、關(guān)系:強(qiáng)引用>軟引用>弱引用>虛引用。垃圾收集算法“標(biāo)記-清除”(Mark-Sweep)算法。先標(biāo)記后清除。不足:一,效率問題;二,空間問題,產(chǎn)生大量不連續(xù)的內(nèi)存碎片。復(fù)制(Copying)算法。為了解決效率問題,出現(xiàn)了復(fù)制算法。將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活的對象復(fù)制到另一塊上,然后再把已使用過的內(nèi)存空間一次清理掉。實(shí)現(xiàn)簡單,運(yùn)行高效,但代價(jià)就是把內(nèi)存縮小為原來的一半來使用。一般這種收集算法主要用來回收新生代,因?yàn)樾律膶ο蠼^大多數(shù)是“朝生夕死”的,存活時(shí)間短,所以并不需要按1:1的比例來劃分空間,而是將內(nèi)存分為
23、一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中一塊Survivor空間。當(dāng)回收時(shí),將Eden和Survivor中還存活的對象一次性地復(fù)制到另一塊Survivor空間上,最后清理掉Eden空間和剛才用過的Survivor空間。Hotspot虛擬機(jī)默認(rèn)的Eden和Survivor的大小比例是8:1?!皹?biāo)記-整理”(Mark-Compact)算法。在老年代中因?yàn)閷ο蟠婊盥矢?、沒有額外空間對它進(jìn)行分配擔(dān)保,所以一般在老年代使用這種收集算法。分代收集算法。沒什么新的思想,只是上面兩種算法的在不同年代的使用。垃圾收集器1.Serial New/Serial Old Se
24、rial/Serial Old收集器是最基本最古老的收集器,它是一個(gè)單線程收集器,并且在它進(jìn)行垃圾收集時(shí),必須暫停所有用戶線程。Serial New收集器是針對新生代的收集器,采用的是Copying算法,Serial Old收集器是針對老年代的收集器,采用的是Mark-Compact算法。它的優(yōu)點(diǎn)是實(shí)現(xiàn)簡單高效,但是缺點(diǎn)是會給用戶帶來停頓。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)行的過程:初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)
26、記、并發(fā)清除。6.G1 G1收集器是當(dāng)今收集器技術(shù)發(fā)展最前沿的成果,它是一款面向服務(wù)端應(yīng)用的收集器,它能充分利用多CPU、多核環(huán)境。因此它是一款并行與并發(fā)收集器,并且它能建立可預(yù)測的停頓時(shí)間模型。G1的運(yùn)行過程:初始標(biāo)記、并發(fā)標(biāo)記、最終標(biāo)記、篩選回收。Hotspot虛擬機(jī)的垃圾收集器(兩個(gè)收集器之間的連線,說明他們之間可以搭配使用)虛擬機(jī)執(zhí)行子系統(tǒng)一 類文件(Class文件)結(jié)構(gòu)實(shí)現(xiàn)語言與平臺無關(guān)的基礎(chǔ)是虛擬機(jī)和字節(jié)碼存儲格式。Class文件是一組以8位(一個(gè)字節(jié))為基礎(chǔ)單位的二進(jìn)制流,各數(shù)據(jù)項(xiàng)嚴(yán)格按順序排列其中,中間沒有添加任何分隔符。根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,CLASS文件格式采用一種
27、類似C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲,這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號數(shù)和表。無符號數(shù)屬于基本的數(shù)據(jù)類型,以u1,u2,u4,u8來分別表示一個(gè)字節(jié),兩個(gè)字節(jié),四個(gè)字節(jié)和8個(gè)字節(jié)的無符號數(shù),無符號數(shù)用來描述數(shù)字,索引引用,數(shù)量值或按照UTF8編碼構(gòu)成字符串?dāng)?shù)。表是由多個(gè)無符號數(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(次版本號)1u2major_versi
28、on(主版本號)1u2constant_pool_count(常量個(gè)數(shù))1 cp_infoconstant_pool(常量池表)constant_pool_count-1u2access_flags(類的訪問控制權(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。用來確定這個(gè)文件是否是Class文件,進(jìn)行身份識別。緊接著魔數(shù)的4個(gè)字節(jié)存儲的是Class文件的版本號:第5和第6個(gè)字節(jié)是次版本號(2個(gè)字節(jié)),第7 和第8個(gè)字節(jié)是主版本號(2個(gè)字節(jié))。Java的版本號是從45(JDK 1.1)開始,每個(gè)JDK版本發(fā)布主版本號加1,高版本號的JDK可以向下兼容以前版本的Class文件,但不能
30、運(yùn)行版本高于自己的CLASS文件。緊接著主次版本號之后的是常量池入口,常量池中常量的數(shù)量不同,用常量池計(jì)數(shù)器(2個(gè)字節(jié))代表常量池容量的計(jì)數(shù)值。計(jì)數(shù)器從1而不是0開始,例如當(dāng)常量池容量為0x0016,十進(jìn)制為22,代表有21個(gè)常量。索引為121。沒有使用0索引是因?yàn)樵诤竺婺承┲赶虺A砍氐乃饕梢酝ㄟ^0索引表示不引用任何一個(gè)常量池項(xiàng)目的意思。常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。字面量的例子有文本字符串,被聲明為final的常量值等。符號引用包含三類常量:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。常量池后面緊接
31、著是類的訪問權(quán)限控制符,類以及父類的全限定名,以及接口的個(gè)數(shù),之后是接口的全限定名,全限定名都是指向常量池的符號引用。再下面就是字段表集合、方法表集合和屬性表集合。二 虛擬機(jī)類加載機(jī)制虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行驗(yàn)證、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制。在Java語言里,類型的加載、連接(驗(yàn)證、準(zhǔn)備、解析)和初始化的過程都是在程序運(yùn)行期間完成的。一個(gè)類的生命周期包括:加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載7個(gè)階段。部分解析可以在初始化開始之后再開始,這樣可以支持java的運(yùn)行時(shí)綁定。Java虛擬機(jī)規(guī)范中并沒
32、有強(qiáng)制規(guī)定什么情況下需要開始類加載過程的第一個(gè)階段:加載,這個(gè)交給虛擬機(jī)自由把握,但卻嚴(yán)格規(guī)定了有且只有5種情況必須立即對類進(jìn)行初始化:1)遇到new創(chuàng)建實(shí)例,getstatic獲取類的靜態(tài)字段,putstatic設(shè)置類的靜態(tài)字段,invokestatic調(diào)用類的靜態(tài)方法2)用java.lang.reflect包方法對類進(jìn)行反射調(diào)用的時(shí)候,如果這個(gè)類沒有初始化過,那么先觸發(fā)其初始化3)初始化一個(gè)類的時(shí)候,如果父類沒有進(jìn)行初始化,那么必須先觸發(fā)其父類的初始化4)當(dāng)虛擬機(jī)啟動的時(shí)候,需要指定一個(gè)執(zhí)行的主類,虛擬機(jī)會先初始化這個(gè)主類5)當(dāng)使用JDK 1.7的動態(tài)語言支持時(shí),如果一個(gè)java.lang
33、.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對應(yīng)的類沒有進(jìn)行初始化,則需要先觸發(fā)其初始化。這5中場景中的行為稱為對一個(gè)類進(jìn)行主動引用。除此之外,所有引用類的方法都不會觸發(fā)初始化,稱為被動引用。例如:通過子類引用父類的靜態(tài)字段,不會導(dǎo)致子類的初始化;用new關(guān)鍵字創(chuàng)建數(shù)組,通過數(shù)組定義來引用類,不會觸發(fā)相應(yīng)的類初始化:AClass a=new AClass10;調(diào)用一個(gè)類的靜態(tài)常量也不會觸發(fā)該類的初始化,因?yàn)檎{(diào)用類在編譯階段就已經(jīng)把常量轉(zhuǎn)化為對自己的常量池的引用。接
34、口的初始化過程與類的初始化過程的顯著區(qū)別:當(dāng)一個(gè)類初始化時(shí),要求其父類全部都已初始化過了,但是一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成了初始化,只要在真正使用到父接口的時(shí)候(如引用父接口中定義的常量)才會初始化。三 類加載的過程類加載的過程:加載、驗(yàn)證、準(zhǔn)備、解析和初始化。加載階段是整個(gè)類加載階段的第一個(gè)階段,在加載階段主要完成3件事情:1)通過類的全限定名來回去定義此類的二進(jìn)制流2)將這個(gè)二進(jìn)制流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)3)在java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口。驗(yàn)證階段,目的是為了確保Class文件的
35、字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全。驗(yàn)證階段大致上完成以下4種驗(yàn)證:Class文件格式的驗(yàn)證,元數(shù)據(jù)的驗(yàn)證,字節(jié)碼的驗(yàn)證,符號引用驗(yàn)證。1)Class文件格式驗(yàn)證為了驗(yàn)證是否符合Class文件的格式,并且能被當(dāng)前版本的虛擬機(jī)處理。這個(gè)階段是驗(yàn)證是基于二進(jìn)制字節(jié)流進(jìn)行的,只有通過了這個(gè)階段的驗(yàn)證后,字節(jié)流才會進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲,所以后面的3個(gè)驗(yàn)證階段全部是基于方法區(qū)的存儲結(jié)構(gòu)進(jìn)行的,不會再直接操作字節(jié)流;2)元數(shù)據(jù)驗(yàn)證是為了對類的元數(shù)據(jù)信息進(jìn)行語義校驗(yàn),保證不存在不符合java語義規(guī)范的元數(shù)據(jù)信息;3)字節(jié)碼驗(yàn)證主要是通過數(shù)據(jù)流和控制流,對類的方法體中的
36、字節(jié)碼進(jìn)行校驗(yàn)分析;4)符號引用驗(yàn)證主要是為了給解析階段符號引用轉(zhuǎn)化為直接引用做準(zhǔn)備,對類自身以外的信息(常量池中的各種符號引用)進(jìn)行匹配性校驗(yàn)。準(zhǔn)備階段,正式為類變量(被static修飾的變量)分配內(nèi)存并設(shè)置初始值。這些變量使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。說明兩點(diǎn):這個(gè)階段僅對類變量分配內(nèi)存,不對實(shí)例變量分配,實(shí)例變量將會在對象實(shí)例化時(shí)隨著對象一起分配在Java堆中;這里的初始值“通常情況”下是數(shù)據(jù)類型的零值?!巴ǔG闆r”:public static int a = 123;/類變量在準(zhǔn)備階段初始化的值為0,而在初始化階段,在<cinit>構(gòu)造方法中會把a(bǔ)的值初始化為123?!?/p>
37、不通常情況”:public static final int a = 123;/用final修飾的類變量在準(zhǔn)備階段,會把a(bǔ)的值初始化為123。解析階段,把虛擬機(jī)在常量池中的符號引用替換為直接引用的過程。解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號引用(后面3種是與JDK 1.7新增的動態(tài)語言支持有關(guān))。初始化階段,是執(zhí)行類構(gòu)造器<cinit>()方法的過程,<cinit>()會自動收集類中的所有類變量以及靜態(tài)語句塊(static),在初始化<cinit>()方法的時(shí)候,虛擬機(jī)會自動調(diào)用父類的<cinit&g
38、t;()方法,接口的<cinit>()方法可以到使用的時(shí)候在去初始化,虛擬機(jī)會保證<cinit>()方法在多線程環(huán)境先被正確的加鎖和同步。還有一個(gè)<init>()方法,這個(gè)方法是實(shí)例構(gòu)造器(類的構(gòu)造函數(shù)),在創(chuàng)建實(shí)例的時(shí)候會被調(diào)用并且初始化。四 Java類加載器(Java ClassLoader)對于任意一個(gè)類,都需要根據(jù)加載它的類加載器和這個(gè)類本身一同確定其在java虛擬機(jī)中的唯一性。每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間。通俗講,判定兩個(gè)類是否相等時(shí),不僅要判斷兩個(gè)類名是否相同,而且要判斷是否由同一個(gè)類加載器加載的。從Java虛擬機(jī)角度來講,只存在兩
39、種不同的加載器:一種是啟動類加載器(Bootstrap ClassLoader),這個(gè)類加載器使用C+語言實(shí)現(xiàn),是虛擬機(jī)的一部分;另一種就是所有其他的類加載器,這些類加載器都由Java語言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全部繼承自抽象類java.lang.ClassLoader。從Java開發(fā)人員的角度來看,類加載器還可以劃分得更細(xì)致一些,絕大部分Java程序都會使用一下3種系統(tǒng)提供的類加載器:啟動類加載器(Bootstrap ClassLoader)、擴(kuò)展類加載器(Extension ClassLoader)、應(yīng)用程序類加載器(App ClassLoader)。1)Bootstrap Class
40、Loader(引導(dǎo)類加載器)負(fù)責(zé)把存放在$JAVA_HOME中jre/lib目錄中的核心庫和基礎(chǔ)庫加載到虛擬機(jī)內(nèi)存中。由C+實(shí)現(xiàn),不是ClassLoader子類,無法被Java程序直接引用。2)Extension ClassLoader(擴(kuò)展類加載器)負(fù)責(zé)加載java平臺中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext或-Djava.ext.dirs指定目錄下所有類庫。開發(fā)者可以直接使用擴(kuò)展類加載器。3)AppClassLoade(應(yīng)用程序類加載器)負(fù)責(zé)加載用戶類路徑classpath中指定的類庫。開發(fā)者可以直接使用擴(kuò)展類加載器。4)User ClassLoader(
41、自定義類加載器)屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader。類加載器雙親委派模型類加載器使用雙親委派模型,這樣當(dāng)要加載一個(gè)類時(shí),首先查找這個(gè)類是否已經(jīng)被加載過,如果沒有,那么類加載器會把這個(gè)類委派給這個(gè)加載器的父類去進(jìn)行加載,如果父類不能加載,那么再自己嘗試加載。加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrapClassLoader逐層檢查,只要某個(gè)classloader已加載過就視為已加載此類,保證此類只被所有ClassLoader加載一次
42、。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。ClassLoader加載類用的是全盤負(fù)責(zé)委托機(jī)制。所謂全盤負(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來加載完成。五 字節(jié)碼執(zhí)行棧幀,是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的虛擬機(jī)棧的棧元素。棧幀中從上
43、到下依次存儲了方法的局部變量表,操作數(shù)棧,動態(tài)鏈接和方法返回地址等信息。每個(gè)方法從調(diào)用開始到調(diào)用結(jié)束,都對應(yīng)著一個(gè)棧幀從入棧到出棧的過程。一個(gè)棧幀需要分配多大的內(nèi)存,在編譯程序代碼期間就確定了。一個(gè)線程中只有棧頂?shù)臈攀怯行У?,稱為當(dāng)前棧幀,這個(gè)棧幀所關(guān)聯(lián)的方法就是當(dāng)前方法。局部變量表,是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。局部變量表的容量以變量槽(Slot)為最小單位。為了節(jié)省棧幀空間,Slot是可以重用,但可能會影響到系統(tǒng)的垃圾收集行為。在方法內(nèi)的局部變量定義了但沒有賦初始值是不能使用的。一般編譯器會檢查到并提示這一點(diǎn)。操作數(shù)棧,也稱為操作棧,是一個(gè)后入先出(L
44、IFO)棧。操作數(shù)棧中元素的數(shù)據(jù)類型必須要與字節(jié)碼指令的序列完全一致。動態(tài)鏈接,是在運(yùn)行期間把符號引用轉(zhuǎn)化為直接引用的過程,相對于靜態(tài)解析(在類加載過程的解析階段把符號引用轉(zhuǎn)化為直接引用)。方法的返回地址,方法返回有兩種類型,一種是正常完成出口,另一種是異常完成出口,方法退出的過程等同于棧幀出棧,因此棧幀出棧的時(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)行過程。Class文件的編譯過程中不包含傳統(tǒng)編譯中的連接
45、步驟,一切方法調(diào)用在Class文件里面存儲的都只是符號引用,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存入口地址(相當(dāng)于之前說的直接引用)。靜態(tài)方法、實(shí)例構(gòu)造器、私有方法、父類方法和final方法,這些方法叫做非虛方法。這類非虛方法的調(diào)用叫做解析。解析調(diào)用一定是個(gè)靜態(tài)的過程,在編譯期間就完全確定,在類加載的解析階段就會把涉及的符號引用全部轉(zhuǎn)變?yōu)榭纱_定的直接引用,不會延遲到運(yùn)行期間再去完成。Human man = new Man(); /Human是變量的靜態(tài)類型 Man是變量的實(shí)際類型靜態(tài)分派:所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動作都稱為靜態(tài)分派,靜態(tài)分派的典型應(yīng)用就是方法重載。靜態(tài)分派發(fā)生在編譯階段,因
46、此確定靜態(tài)分派的動作實(shí)際上不是由虛擬機(jī)來執(zhí)行的。動態(tài)分派:在運(yùn)行期根據(jù)實(shí)際類型確定方法的執(zhí)行版本的分派過程稱為動態(tài)分派,動態(tài)分派的典型應(yīng)用就是方法重寫。宗量:方法的接受者和方法的參數(shù)統(tǒng)稱為方法的宗量。單分派:根據(jù)一個(gè)宗量對目標(biāo)方法進(jìn)行選擇多分派:根據(jù)多個(gè)宗量對目標(biāo)方法進(jìn)行選擇Java是一種靜態(tài)多分派,動態(tài)單分派語言。類的方法區(qū)會保存一張?zhí)摲椒ū?,存放方法的?shí)際入口地址,如果沒有重寫父類的方法,那么入口與父類的一樣,如果重寫了父類的方法,那么方法的入口地址指向自己的方法入口地址。方法表一般在類加載的連接階段進(jìn)行初始化,準(zhǔn)備了類變量的初始值之后,虛擬機(jī)會把該類的方法表也初始化完畢,這是java實(shí)現(xiàn)
47、動態(tài)分派方法。方法執(zhí)行,Java虛擬機(jī)的執(zhí)行引擎在執(zhí)行Java代碼的時(shí)候都有解釋執(zhí)行(通過解釋器執(zhí)行)和編譯執(zhí)行(通過即時(shí)編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇?;跅5慕忉屍鲌?zhí)行在Class文件格式與執(zhí)行引擎這部分中,用戶的程序能直接影響的內(nèi)容并不太多,Class文件以何種格式存儲,類型何時(shí)加載,如何連接,以及虛擬機(jī)如何執(zhí)行字節(jié)碼指令等都是由虛擬機(jī)直接控制的行為,用戶程序無法進(jìn)行干預(yù)。能通過程序進(jìn)行操作的,主要是字節(jié)碼生成和類加載器這兩部分。程序編譯一 編譯期Java語言的“編譯期”其實(shí)是一段不確定的操作過程,因?yàn)樗赡苁侵敢粋€(gè)前端編譯器把*.java文件轉(zhuǎn)變成*.class文件的過程;也可能是指
48、虛擬機(jī)的后端運(yùn)行期編譯器把字節(jié)碼轉(zhuǎn)變成機(jī)器碼的過程。Java的即時(shí)編譯器在運(yùn)行期的優(yōu)化過程對于程序運(yùn)行來說更重要,而前端編譯器在編譯期的優(yōu)化過程對于程序編碼來說關(guān)系更加密切。Javac編譯器,是Sun公司的前端編譯期,它本身就是一個(gè)由Java語言編寫的程序。從Javac的代碼來看,編譯過程大致可以分為3個(gè)過程:解析與填充符號表過程、插入式注解處理器的注解處理過程和分析與字節(jié)碼生成過程。Javac的編譯過程過程1.1:解析(詞法、語法分析)過程1.2:填充到符號表過程2:執(zhí)行注解處理過程3.1:標(biāo)注檢查過程3.2:數(shù)據(jù)及控制流分析過程3.3:解語法糖(常見語法糖:泛型、自動裝箱/拆箱、變長參數(shù)等
49、)過程3.4:字節(jié)碼生成二 運(yùn)行期前面Javac這類將Java源代碼轉(zhuǎn)變成字節(jié)碼的編譯器一般稱為“前端編譯器”,是因?yàn)樗煌瓿闪藦某绦虻街虚g字節(jié)碼的生成,而在此之后,還有一組在虛擬機(jī)內(nèi)部的“后端編譯器”完成了從字節(jié)碼生成機(jī)器碼的過程,這類編譯器一般稱作為即時(shí)編譯器或JIT編譯器,工作在程序的運(yùn)行期。這類編譯器的編譯速度及編譯結(jié)果的優(yōu)劣,是衡量一個(gè)虛擬機(jī)性能很重要的指標(biāo)。Java程序最初是通過解釋器進(jìn)行解釋執(zhí)行的,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼的運(yùn)行特別頻繁時(shí),就會把這些代碼認(rèn)定為“熱點(diǎn)代碼”。為了提高熱點(diǎn)代碼的執(zhí)行效率,在運(yùn)行時(shí),虛擬機(jī)將會把這些熱點(diǎn)代碼編譯成與本地平臺相關(guān)的機(jī)器碼,并進(jìn)行各層次優(yōu)
50、化,完成這個(gè)任務(wù)的編譯器就是即時(shí)編譯器。(C/C+:靜態(tài)優(yōu)化編譯器)Hotspot虛擬機(jī)采用的是解釋器與編譯器并存的架構(gòu)。解釋器和編譯器兩者各有優(yōu)勢:當(dāng)程序需要快速啟動和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用,省去編譯時(shí)間,立即執(zhí)行。在程序運(yùn)行后,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼后,可以獲取更高的執(zhí)行效率。Hotspot虛擬機(jī)中內(nèi)置了兩個(gè)即時(shí)編譯器,分別稱為Client Compiler 和Server Compiler(簡稱C1和C2)。用C1可以獲取更高的編譯速度,用C2可以獲取更好的編譯質(zhì)量。程序使用哪個(gè)編譯器,取決虛擬機(jī)運(yùn)行模式,虛擬機(jī)會根據(jù)自身版本和宿主
51、機(jī)器的硬件性能自動選擇運(yùn)行模式,用戶也可以自己使用參數(shù)強(qiáng)制指定運(yùn)行模式。在運(yùn)行過程中能被即時(shí)編譯器編譯的“熱點(diǎn)代碼”有兩類:被多次調(diào)用的方法;被多次執(zhí)行的循環(huán)體。判斷一段代碼是不是熱點(diǎn)代碼,需不需要觸發(fā)即時(shí)編譯,這樣的行為稱為熱點(diǎn)探測。主要的熱點(diǎn)探測判定方式:基于采樣的熱點(diǎn)探測和基于計(jì)數(shù)器的熱點(diǎn)探測(Hotspot)?;谟?jì)數(shù)器的熱點(diǎn)探測方式為每個(gè)方法準(zhǔn)備了兩個(gè)計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(Client模式1500次,Server模式10000次,計(jì)數(shù)熱度衰減)和回邊計(jì)數(shù)器(Client模式13995次,Server模式10700次,沒有計(jì)數(shù)熱度衰減)。編譯優(yōu)化技術(shù):公共子表達(dá)式消除、數(shù)組范圍檢查消
52、除、方法內(nèi)聯(lián)、逃逸分析Javac字節(jié)碼的編譯器與虛擬機(jī)內(nèi)的JIT編譯器的執(zhí)行過程合并起來其實(shí)就等同于一個(gè)傳統(tǒng)編譯器所執(zhí)行的編譯過程。高效并發(fā)一 Java內(nèi)存模型Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣底層細(xì)節(jié)。此處的變量與Java編程時(shí)所說的變量不一樣,指包括了實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但是不包括局部變量與方法參數(shù),后者是線程私有的,不會被共享。Java內(nèi)存模型中規(guī)定了:所有的變量都存儲在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程使用到的變量到主內(nèi)存副本拷貝,線程對變量的所有操作(讀取、
53、賦值)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。不同線程之間無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞均需要在主內(nèi)存來完成。線程、主內(nèi)存和工作內(nèi)存的交互關(guān)系如下圖所示。這里的主內(nèi)存、工作內(nèi)存與Java內(nèi)存區(qū)域的Java堆、棧、方法區(qū)不是同一層次內(nèi)存劃分。如果非要對應(yīng)起來,主內(nèi)存主要對應(yīng)于Java堆中的對象實(shí)例數(shù)據(jù)部分,而工作內(nèi)存則對應(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)存模型定義了以下八種操作來完成:· lock(鎖定):作用于主
54、內(nèi)存的變量,把一個(gè)變量標(biāo)識為一條線程獨(dú)占狀態(tài)。· unlock(解鎖):作用于主內(nèi)存變量,把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。· read(讀?。鹤饔糜谥鲀?nèi)存變量,把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動作使用· load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。· use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)將會執(zhí)行這個(gè)操作。· assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。· store(存儲):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write的操作。· write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存的變量中。如果要把一個(gè)變量從主內(nèi)存中復(fù)制到工作內(nèi)存,就需
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 畢業(yè)課題申報(bào)書范例
- 區(qū)級教師課題申報(bào)書
- 合同范本修訂
- 合伙分紅合同范本
- 微課題申報(bào)書
- 教改課題申報(bào)書怎么填
- 銜接課題申報(bào)書范文
- 員工持股合同范本
- 國家申報(bào)書課題名稱結(jié)構(gòu)
- 個(gè)人購酒合同范本
- 2025年共青科技職業(yè)學(xué)院單招職業(yè)適應(yīng)性測試題庫完整版
- 2025年上半年潛江市城市建設(shè)發(fā)展集團(tuán)招聘工作人員【52人】易考易錯(cuò)模擬試題(共500題)試卷后附參考答案
- 旋轉(zhuǎn)類機(jī)電設(shè)備故障預(yù)測、診斷研究
- 2024年江西應(yīng)用工程職業(yè)學(xué)院單招職業(yè)技能測試題庫標(biāo)準(zhǔn)卷
- 新媒體營銷(第三版) 課件全套 林海 項(xiàng)目1-6 新媒體營銷認(rèn)知-新媒體營銷數(shù)據(jù)分析
- 愚公移山英文 -中國故事英文版課件
- 美制統(tǒng)一螺紋表UNC_UNF DS
- 2012年北京大學(xué)醫(yī)學(xué)部外國留學(xué)生本科入學(xué)考試
- 七年級英語閱讀理解50篇(附答案)
- 乙酸乙酯的制備ppt課件
- 音樂之聲中英文臺詞
評論
0/150
提交評論