摘要:前言本文內(nèi)容基本摘抄自深入理解虛擬機(jī),以供復(fù)習(xí)之用,沒有多少參考價(jià)值。此區(qū)域是唯一一個(gè)在虛擬機(jī)規(guī)范中沒有規(guī)定任何情況的區(qū)域。堆是所有線程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。虛擬機(jī)上把方法區(qū)稱為永久代。
前言
本文內(nèi)容基本摘抄自《深入理解Java虛擬機(jī)》,以供復(fù)習(xí)之用,沒有多少參考價(jià)值。想要更詳細(xì)了解請(qǐng)參考原書。
第二章 1.運(yùn)行時(shí)數(shù)據(jù)區(qū)域程序計(jì)數(shù)器可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器。如果線程執(zhí)行Java方法,計(jì)數(shù)器記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址;如果執(zhí)行Native方法,計(jì)數(shù)器值為空。此區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
Java虛擬機(jī)棧也是線程私有的,生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;如果虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠內(nèi)存,就會(huì)拋出OutOfMemoryError異常。
本地方法棧類似虛擬機(jī)棧,區(qū)別是本地方法棧為虛擬機(jī)使用到的Native方法服務(wù)。
Java堆是所有線程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。所有對(duì)象實(shí)例以及數(shù)組都要在堆上分配。Java堆是GC管理的主要區(qū)域。從內(nèi)存回收角度,Java堆可以細(xì)分為新生代和老年代,如果使用復(fù)制算法收集,還可以分為Eden空間、From Survivor空間、To Survivor空間。從內(nèi)存分配角度,線程共享的Java堆可能劃分出多個(gè)線程私有的分配緩沖區(qū)(TLAB)。如果堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無(wú)法再擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。
方法區(qū)是所有線程共享區(qū)域,用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。HotSpot虛擬機(jī)上把方法區(qū)稱為永久代。但用永久代實(shí)現(xiàn)方法區(qū)有問題,例如String.intern()在不同虛擬機(jī)有不同表現(xiàn)。JDK1.7已經(jīng)把原本放在永久代的字符串常量池移出。當(dāng)方法區(qū)無(wú)法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常。
運(yùn)行時(shí)常量池是方法區(qū)的一部分。Class文件中除了類的版本、字段、方法、接口等描述信息外,還有常量池,這部分將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。運(yùn)行時(shí)常量池相對(duì)于Class文件常量池的另外一個(gè)特征是動(dòng)態(tài)性,并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中(intern())。
直接內(nèi)存不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)一部分。JDK NIO引入了一種基于通道和緩沖區(qū)的I/O方式,它可以使用Native函數(shù)直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。分配的直接內(nèi)存導(dǎo)致各內(nèi)存區(qū)域總和大于物理內(nèi)存限制從而導(dǎo)致動(dòng)態(tài)擴(kuò)展時(shí)出現(xiàn)OutOfMenmoryError。
2.關(guān)于對(duì)象對(duì)象的創(chuàng)建:
對(duì)象的內(nèi)存布局:
對(duì)象在內(nèi)存中存儲(chǔ)的布局分為:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。對(duì)象頭分為對(duì)象運(yùn)行時(shí)數(shù)據(jù)和類型指針。
對(duì)象運(yùn)行時(shí)數(shù)據(jù)包括HashCode、GC分代年齡和鎖狀態(tài)標(biāo)志位等。類型指針即對(duì)象指向它的類元數(shù)據(jù)的指針。另外,如果對(duì)象是一個(gè)Java數(shù)組,那在對(duì)象頭中還有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)。
實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,也是在程序代碼中所定義的各種類型的字段內(nèi)容。
對(duì)齊補(bǔ)充不是必然存在,起著占位符作用。保證對(duì)象起始地址是8字節(jié)的整數(shù)倍。
對(duì)象的訪問:
Java程序通過棧上的reference數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。訪問方式分為使用句柄和直接指針兩種。使用句柄,Java堆中會(huì)劃分出一塊內(nèi)存作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息。如果使用直接指針訪問,那么Java堆對(duì)象的布局中就要放置訪問類型數(shù)據(jù)的相關(guān)信息。
3.部分虛擬機(jī)啟動(dòng)參數(shù)
-Xms:堆的最小值
-Xmx:堆的最大值
-Xmn::堆分配給新生代的大小
-XX:+HeapDumpOnOutOfMemoryError:虛擬機(jī)出現(xiàn)內(nèi)存溢出異常時(shí)Dump出當(dāng)前內(nèi)存堆轉(zhuǎn)儲(chǔ)快照
-Xss:棧容量
-XX:PermSize:永久代最小值
-XX:MaxPermSize:永久代最大值
-XX:MaxDirectMemorySize:本機(jī)直接內(nèi)存大小,如果不指定,默認(rèn)與-Xmx一樣。
1.對(duì)象存亡
判斷對(duì)象是否存活有兩種方法,引用計(jì)數(shù)算法和可達(dá)性分析。
引用計(jì)數(shù)算法:給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。引用計(jì)數(shù)算法的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,判定效率也高。缺點(diǎn)是它很難解決對(duì)象之間相互循環(huán)引用的問題。
可達(dá)性分析算法:通過一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí),則證明此對(duì)象是不可用的。可作為GC Roots的對(duì)象包括:虛擬機(jī)棧中引用的對(duì)象、方法區(qū)中類靜態(tài)屬性引用的對(duì)象、方法區(qū)中常量引用的對(duì)象、本地方法棧中JNI引用的對(duì)象。
對(duì)象引用:
強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)。
強(qiáng)引用只要存在,垃圾收集器永遠(yuǎn)不會(huì)回收被引用對(duì)象。軟引用用來(lái)描述還有用但并非必須的對(duì)象。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍進(jìn)行二次回收。弱引用也用來(lái)描述非必須對(duì)象,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前。一個(gè)對(duì)象是否有虛引用的存在不影響生存時(shí)間,也無(wú)法通過虛引用取得對(duì)象實(shí)例。虛引用唯一目的是在垃圾回收時(shí)收到一個(gè)系統(tǒng)通知。
對(duì)象存亡:
宣告對(duì)象死亡至少要經(jīng)歷兩次標(biāo)記過程:如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會(huì)被第一次標(biāo)記并進(jìn)行一次篩選是否有必要執(zhí)行finalize()方法。若對(duì)象沒有覆蓋此方法或曾經(jīng)調(diào)用過此方法,則沒必要執(zhí)行。如果在finalize()方法中重新與引用鏈建立聯(lián)系,則對(duì)象存活,否則 進(jìn)行第二次標(biāo)記。
方法區(qū)的回收:
永久代回收包括廢棄常量和無(wú)用的類。其中類的回收條件較為苛刻:該類所有實(shí)例已回收;該類的類加載器已回收;該類的Class對(duì)象沒有被引用。滿足這三個(gè)條件才可以回收,而不是必然回收。
2.垃圾收集算法
標(biāo)記-清除算法:標(biāo)記和清除效率都不高且標(biāo)記清除后產(chǎn)生大量不連續(xù)內(nèi)存碎片。
復(fù)制算法:堆分為一塊Eden和兩塊Survivor,大小為8:1:1,每次使用Eden和其中一塊Survivor,回收時(shí),將上面存活的對(duì)象復(fù)制到另外一塊Survivor上,清理Eden和剛才使用過的Survivor空間。當(dāng)Survivor空間不夠時(shí),需要依賴?yán)夏甏M(jìn)行分配擔(dān)保。
標(biāo)記-整理算法:復(fù)制算法在對(duì)象存活率高時(shí)需要較多復(fù)制操作,效率較低。標(biāo)記整理類似標(biāo)記清除,不過標(biāo)記后讓所有存活對(duì)象移向一端,然后清理掉邊界以外的內(nèi)存。
分代收集算法:根據(jù)對(duì)象存活周期不同將內(nèi)存劃分為幾塊。新生代使用復(fù)制算法,老年代使用標(biāo)記清除和標(biāo)記整理。
3.算法實(shí)現(xiàn)
可達(dá)性分析時(shí)間敏感:
可達(dá)性分析從GC Roots中查找引用鏈,GC Roots節(jié)點(diǎn)包括全局性引用與執(zhí)行上下文,而僅僅方法區(qū)就有數(shù)百兆,如果逐個(gè)檢查,會(huì)耗費(fèi)大量時(shí)間。可達(dá)性分析還需要GC Roots停頓。
安全點(diǎn):
HotSpot使用一組OopMap的數(shù)據(jù)結(jié)構(gòu)存放對(duì)象引用。但HotSpot沒有為每條指令生成OopMap,只在安全點(diǎn)記錄信息。一般“長(zhǎng)時(shí)間執(zhí)行”的指令,如方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)會(huì)產(chǎn)生SafePoint。GC時(shí)讓所有線程在最近安全點(diǎn)停頓分為搶先式中斷和主動(dòng)式中斷。
安全區(qū)域:
線程處于Sleep或Blocked狀態(tài),無(wú)法響應(yīng)JVM的中斷請(qǐng)求,就無(wú)法執(zhí)行到安全點(diǎn)掛起。安全區(qū)域是指一段代碼之中,引用關(guān)系不會(huì)發(fā)生變化,這個(gè)區(qū)域任意地方開始GC都是安全的。
4.垃圾收集器
Serial收集器:
單線程收集器,不僅僅只會(huì)使用一個(gè)CPU或一條收集線程去完成垃圾收集工作,它在進(jìn)行垃圾收集時(shí)必須暫停所有其他工作線程。優(yōu)點(diǎn)是簡(jiǎn)單而高效。
ParNew收集器:
多線程收集器,除了多線程收集都與Serial一樣。優(yōu)點(diǎn)是除了Serial,只有它能與CMS配合工作。
Parallel Scavenge收集器:
多線程收集器,目標(biāo)是吞吐量?jī)?yōu)先(CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值)。
Serial Old收集器:
Serial老年代版本,單線程收集器,“標(biāo)記-整理”算法。
Parallel Old收集器:
Parallel Scavenge的老年代版本,多線程收集,“標(biāo)記-整理”算法,注重吞吐量。
CMS收集器:
以獲取最短回收停頓時(shí)間為目標(biāo)的收集器?!皹?biāo)記-清除”算法,分為:初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除。初始標(biāo)記和重新標(biāo)記仍然需要“Stop The World”。
初始標(biāo)記僅僅標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快;并發(fā)標(biāo)記就是進(jìn)行GC Roots Tracing的過程;重新標(biāo)記是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,時(shí)間比初始標(biāo)記稍長(zhǎng),比并發(fā)標(biāo)記短。由于整個(gè)過程耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作。所以CMS的內(nèi)存回收是與用戶線程一起“并發(fā)”執(zhí)行的。
CMS有3個(gè)缺點(diǎn):對(duì)CPU資源非常敏感;無(wú)法處理浮動(dòng)垃圾、會(huì)產(chǎn)生大量空間碎片(標(biāo)記-清除)。
G1收集器:
G1的優(yōu)點(diǎn)是并行與并發(fā)、分代收集、空間整合、可預(yù)測(cè)的停頓?;厥者^程分為:初始標(biāo)記、并發(fā)標(biāo)記、最終標(biāo)記、篩選回收。
5.內(nèi)存分配與回收策略
對(duì)象優(yōu)先在Eden分配
大對(duì)象直接進(jìn)入老年代
長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
動(dòng)態(tài)對(duì)象年齡判定
空間分配擔(dān)保
第四章1.JDK命令行工具
jps:虛擬機(jī)進(jìn)程狀況工具??梢粤谐稣谶\(yùn)行的虛擬機(jī)進(jìn)程,并顯示虛擬機(jī)執(zhí)行主類名稱以及這些進(jìn)程的本地虛擬機(jī)唯一ID。
jstat:虛擬機(jī)統(tǒng)計(jì)信息監(jiān)視工具。可以顯示本地或者遠(yuǎn)程虛擬機(jī)進(jìn)程中的類裝載、內(nèi)存、垃圾收集、JIT編譯等運(yùn)行數(shù)據(jù)。
jinfo:Java配置信息工具??梢詫?shí)時(shí)地查看和調(diào)整虛擬機(jī)各項(xiàng)參數(shù)。
jmap:java內(nèi)存映像工具。用于生成堆轉(zhuǎn)儲(chǔ)快照。
jhat:虛擬機(jī)堆轉(zhuǎn)儲(chǔ)快照分析工具。分析jmap生成的快照。
jstack:Java堆棧跟蹤工具。用于生成虛擬機(jī)當(dāng)時(shí)的線程快照,就是當(dāng)前虛擬機(jī)內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合。
HSDIS:JIT生成代碼反匯編。
2.JDK可視化工具
jconsole:Java監(jiān)視與管理控制臺(tái)。
VisualVM:多合一故障處理工具。
1.Class類文件的結(jié)構(gòu)
Class文件格式采用一種類似于C語(yǔ)言結(jié)構(gòu)體的偽結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù),這種偽結(jié)構(gòu)只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表。無(wú)符號(hào)數(shù)分為u1、u2、u4、u8。表由多個(gè)無(wú)符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型。
魔數(shù)
Class文件版本
訪問標(biāo)志
類索引、父類索引與接口索引集合
字段表集合
方法表集合
屬性表集合
其中,屬性表集合有Code屬性、Exceptions屬性、LineNumberTable屬性、LocalVariableTable屬性、SourceFile屬性、ConstantValue屬性、InnerClasses屬性、Deprecated及Synthetic屬性、stackMapTable屬性、Signature屬性、BootstrapMethods屬性等。
2.字節(jié)碼指令
加載和存儲(chǔ)指令
運(yùn)算指令
類型轉(zhuǎn)換指令
對(duì)象創(chuàng)建與訪問指令
操作數(shù)棧管理指令
控制轉(zhuǎn)移指令
方法調(diào)用和返回指令
異常處理指令
同步指令
第七章1.類加載機(jī)制
虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制。Java語(yǔ)言里,類型加載、連接和初始化過程都是在程序運(yùn)行期間完成的,因此Java有高度的靈活性。
2.類加載的時(shí)機(jī)
加載、驗(yàn)證、準(zhǔn)備、初始化、卸載按部就班的開始,解析階段可以在初始化之后開始(為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定)。
立即對(duì)類進(jìn)行初始化的五種情況:
遇到new(實(shí)例化對(duì)象)、getstatic(讀取類的靜態(tài)字段)、putstatic(設(shè)置類的靜態(tài)字段)、invokestatic(調(diào)用一個(gè)類的靜態(tài)方法)4條字節(jié)碼指令時(shí);
使用reflect對(duì)類反射調(diào)用時(shí)
初始化一個(gè)類時(shí),如果其父類還未初始化(對(duì)于接口來(lái)說(shuō),只有在真正使用父接口時(shí)才會(huì)初始化)
虛擬機(jī)啟動(dòng)時(shí)指定執(zhí)行主類
使用動(dòng)態(tài)語(yǔ)言支持時(shí),MethodHandle的解析結(jié)果的方法句柄所對(duì)應(yīng)的類沒有進(jìn)行過初始化
通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化;通過數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化;常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的初始化。
3.類加載的過程
加載:
通過一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流;
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);
在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口(存放在方法區(qū));
驗(yàn)證:
文件格式驗(yàn)證
元數(shù)據(jù)驗(yàn)證
字節(jié)碼驗(yàn)證
符號(hào)引用驗(yàn)證
準(zhǔn)備:
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值(通常情況為零值,final則直接賦值)的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。
解析:
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程。符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān);直接引用可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄,直接引用和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)。
類或接口的解析
字段解析
類方法解析
接口方法解析
方法類型解析
方法句柄解析
調(diào)用點(diǎn)限定符解析
初始化:
前面的類加載過程中,除了加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼(字節(jié)碼)。初始化階段是執(zhí)行類構(gòu)造器
4.類加載器
類加載階段中的“通過一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流”就是由類加載器實(shí)現(xiàn)的。且類加載器左右不限于此,任意一個(gè)類都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類加載器都擁有一個(gè)獨(dú)立的類名稱空間。
雙親委派模型:
從JVM角度只存在兩種類加載器:?jiǎn)?dòng)類加載器(Bootstrap ClassLoader),由C++實(shí)現(xiàn);所有其他類加載器,由Java實(shí)現(xiàn)。從開發(fā)角度分為四類:?jiǎn)?dòng)類加載器(Bootstrap ClassLoader),
雙親委派模型的工作過程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,所有的加載請(qǐng)求最后都應(yīng)該傳送到頂層的啟動(dòng)類加載器,只有當(dāng)父加載器反饋無(wú)法加載(搜索范圍沒找到需要的類)時(shí),子加載器才會(huì)嘗試自己去加載。
雙親委派模型的好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系,保證了Java程序的穩(wěn)定運(yùn)作。
破壞雙親委派模型:
面對(duì)已經(jīng)存在的用戶自定義類加載器的實(shí)現(xiàn)代碼,為了向前兼容,JDK1.2之后的java.lang.ClassLoader添加了一個(gè)新的protected方法findClass()。
雙親委派模型很好地解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問題,但基礎(chǔ)類可能會(huì)回調(diào)用戶代碼。因此引入線程上下文類加載器,可以通過java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,默認(rèn)為應(yīng)用程序類加載器。這其實(shí)是父類加載器請(qǐng)求子類加載器完成類加載,打破了雙親委派模型。
用戶對(duì)程序動(dòng)態(tài)性的追求導(dǎo)致破壞(代碼熱替換、模塊熱部署)。OSGI實(shí)現(xiàn)模塊化部署的關(guān)鍵是它自定義的類加載器機(jī)制的實(shí)現(xiàn)。每一個(gè)程序模塊(Bundle)都有一個(gè)自己的類加載器,當(dāng)需要更換一個(gè)Bundle時(shí),就把Bundle連同類加載器一起換掉以實(shí)現(xiàn)代碼的熱替換。
第八章1.運(yùn)行時(shí)棧幀結(jié)構(gòu)
棧幀存儲(chǔ)了方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接和方法返回地址等信息。每一個(gè)方法從調(diào)用開始至執(zhí)行完成的過程,都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里面從入棧到出棧的過程。
局部變量表:
局部變量表是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。在Java程序編譯為Class文件時(shí),就在方法的Code屬性的max_locals數(shù)據(jù)項(xiàng)中確定了該方法需要分配的局部變量表的最大容量。
虛擬機(jī)通過索引定位的方式使用局部變量表,索引值的范圍是從0開始至局部變量表最大的Slot數(shù)量。如果執(zhí)行的是實(shí)例方法,局部變量表中第0位索引的Slot默認(rèn)是用于傳遞方法所屬對(duì)象實(shí)例的引用,可通過“this”訪問。
存在一種特殊情形,對(duì)象占用內(nèi)存大、此方法的的棧幀長(zhǎng)時(shí)間不回收、方法調(diào)用次數(shù)達(dá)不到JIT編譯條件,手動(dòng)將不再使用的變量設(shè)置null是有用的。
局部變量不像類變量有準(zhǔn)備階段,沒有賦初始值不能使用。
操作數(shù)棧:
操作數(shù)棧是一個(gè)后入先出棧。同局部變量表一樣,操作數(shù)棧的最大深度也在編譯的時(shí)候?qū)懭氲紺ode屬性的max_stack數(shù)據(jù)項(xiàng)中。
當(dāng)一個(gè)方法剛剛開始執(zhí)行時(shí),這個(gè)方法的操作數(shù)棧是空的,在方法的執(zhí)行過程中,會(huì)有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容,也就是入棧出棧操作。
概念模型中,兩個(gè)棧幀作為虛擬機(jī)棧的元素,是完全相互獨(dú)立的。但在大多數(shù)虛擬機(jī)的實(shí)現(xiàn)里都會(huì)做一些優(yōu)化處理,令兩個(gè)棧幀出現(xiàn)一部分重疊。
動(dòng)態(tài)鏈接:
Class文件的常量池中存有大量的符號(hào)引用,這些符號(hào)引用一部分會(huì)在類加載階段或者第一次使用時(shí)轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化稱為靜態(tài)解析;另外一部分將在每一次運(yùn)行期間轉(zhuǎn)化為直接引用,稱為動(dòng)態(tài)鏈接。
方法返回地址:
方法開始執(zhí)行后,有兩種退出方式:正常完成出口和異常完成出口。方法返回時(shí)需要在棧幀中保存一些信息,用來(lái)幫助恢復(fù)它的上層方法的執(zhí)行狀態(tài)。方法正常退出時(shí),調(diào)用者的PC計(jì)數(shù)器的值可以作為返回地址,會(huì)在棧幀中保存;方法異常退出時(shí),返回地址是要通過異常處理器表來(lái)確定,棧幀中不保存。
附加信息:
允許增加附加信息到棧幀中。
2.方法調(diào)用
方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本,暫不涉及方法內(nèi)部運(yùn)行。
Class文件的編譯過程中不包含傳統(tǒng)編譯中的連接步驟,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用,而不是方法在內(nèi)存的入口地址。需要在類加載期間,甚至到運(yùn)行期間才能確定目標(biāo)方法的直接引用。
Java虛擬機(jī)五條方法調(diào)用指令:
invokestatic:調(diào)用靜態(tài)方法;
invokespecial:調(diào)用實(shí)例構(gòu)造器
invokevirtual:調(diào)用所有的虛方法。
invokeinterface:調(diào)用接口方法,會(huì)在運(yùn)行時(shí)再確定一個(gè)實(shí)現(xiàn)此接口的對(duì)象。
invokedynamic:現(xiàn)在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法。
前4條調(diào)用指令分派邏輯是固化在Java虛擬機(jī)內(nèi)部的,而invokeddynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的。
解析:
在類加載的解析階段,會(huì)將其中的一部分符號(hào)引用轉(zhuǎn)化為直接引用,這部分調(diào)用目標(biāo)在程序代碼寫好、編譯器進(jìn)行編譯時(shí)就必須確定下來(lái)。這類方法的調(diào)用稱為解析。
只要能被invokestatic和invokespecial指令調(diào)用的方法,都可以在解析階段中確定唯一的調(diào)用版本,在類加載的時(shí)候就會(huì)把符號(hào)引用解析為該方法的直接引用。
Java中的非虛方法除了使用invokestatic、invokespecial調(diào)用的方法之外,還有final修飾的方法,雖然final使用invokevirtual調(diào)用。
分派:
解析調(diào)用一定是靜態(tài)過程,在編譯期間完全確定,在類加載的解析階段就會(huì)把符號(hào)引用替換為直接引用;而分派調(diào)用可能是靜態(tài)或動(dòng)態(tài)。
靜態(tài)分派:所有依賴靜態(tài)類型來(lái)定位方法執(zhí)行版本的分派動(dòng)作稱為靜態(tài)分派。靜態(tài)分派的典型應(yīng)用是方法重載。靜態(tài)分派發(fā)生在編譯階段,因此確定靜態(tài)分派的動(dòng)作實(shí)際上不是由虛擬機(jī)來(lái)執(zhí)行的。另外,編譯器雖然能確定出方法的重載版本,但有時(shí)并不唯一,原因是字面量不需要定義,所以字面量沒有顯式的靜態(tài)類型。
動(dòng)態(tài)分派:運(yùn)行期間根據(jù)實(shí)際類型確定方法執(zhí)行版本的分派過程稱為動(dòng)態(tài)分派。動(dòng)態(tài)分派的典型應(yīng)用是方法重寫。invokevirtual指令執(zhí)行的第一步就是在運(yùn)行期確定接收者的實(shí)際類型。
單分派與多分派:方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量,根據(jù)分派基于多少宗量,可以將分派劃分為單分派和多分派。Java語(yǔ)言是一門靜態(tài)多分派、動(dòng)態(tài)單分派的語(yǔ)言。
虛擬機(jī)動(dòng)態(tài)分派實(shí)現(xiàn):使用虛方法表索引來(lái)代替元數(shù)據(jù)查找以提高性能。虛方法表中存放著各個(gè)方法的實(shí)際入口地址。
3.動(dòng)態(tài)類型語(yǔ)言支持
動(dòng)態(tài)類型語(yǔ)言的關(guān)鍵特征是它的類型檢查的主體過程是在運(yùn)行期而不是編譯期。編譯器就進(jìn)行類型檢查過程的語(yǔ)言就是最常用的靜態(tài)類型語(yǔ)言?!白兞繜o(wú)類型而變量值才有類型”也是動(dòng)態(tài)類型語(yǔ)言的一個(gè)重要特征。
靜態(tài)語(yǔ)言在編譯期確定類型,最顯著的好處是編譯器可以提供嚴(yán)謹(jǐn)?shù)念愋蜋z查;動(dòng)態(tài)類型語(yǔ)言在運(yùn)行期確定類型,可以提供更大的安全性,也使代碼更加清晰和簡(jiǎn)潔。
java.lang.invoke:
這個(gè)包的主要目的是在之前JVM單純依靠符號(hào)引用來(lái)確定調(diào)用的目標(biāo)方法這種方式之外,提供一種新的動(dòng)態(tài)確定目標(biāo)方法的機(jī)制,稱為MethodHandle。
MethodHandle與Reflection相似之處很多,區(qū)別如下:Reflection是在模擬Java代碼層次的方法調(diào)用,MethodHand是在模擬字節(jié)碼層次的調(diào)用;Reflection是重量級(jí),MethodHandle是輕量級(jí);由于MethodHandle是對(duì)字節(jié)碼的模擬,所以虛擬機(jī)在這方面的優(yōu)化可以被MethodHandle借鑒。
invokedynamic:
invokedynamic指令與MethodHandle機(jī)制的作用一樣,解決原有4條“invoke*”指令方法分派規(guī)則固化在虛擬機(jī)之中的問題,把如何查找目標(biāo)方法的決定權(quán)從虛擬機(jī)轉(zhuǎn)嫁到具體用戶代碼之中。區(qū)別是一個(gè)采用上層Java代碼和API實(shí)現(xiàn),另一個(gè)用字節(jié)碼和Class中其他屬性、常量來(lái)完成。
4.基于棧的字節(jié)碼解釋執(zhí)行引擎
Java語(yǔ)言中,Javac編譯器完成了程序代碼經(jīng)過詞法分析、語(yǔ)法分析到抽象語(yǔ)法樹,再遍歷語(yǔ)法樹生成線性的字節(jié)碼指令流的過程。這一部分在JVM之外進(jìn)行,而解釋器和解釋執(zhí)行在JVM內(nèi)部,所以Java的編譯是半獨(dú)立的實(shí)現(xiàn)。
Java編譯器輸出的指令流,是一種基于棧的指令集架構(gòu)?;跅5闹噶罴饕獌?yōu)點(diǎn)就是可移植,缺點(diǎn)是執(zhí)行速度相對(duì)較慢。
第十章Javac編譯器
從Javac的代碼來(lái)看,編譯過程分為3個(gè)過程:
解析與填充符號(hào)表過程:詞法、語(yǔ)法分析;填充符號(hào)表
插入式注解處理器的注解處理過程
語(yǔ)義分析與字節(jié)碼生成過程:標(biāo)注檢查、數(shù)據(jù)及控制流分析、解語(yǔ)法糖、字節(jié)碼生成
Java語(yǔ)法糖
泛型和類型擦除:泛型的本質(zhì)是參數(shù)化類型,即所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)。Java語(yǔ)言中的泛型只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來(lái)的原生類型,并在相應(yīng)地方插入了強(qiáng)制類型轉(zhuǎn)化代碼,ArrayList
自動(dòng)裝箱、拆箱與循環(huán)遍歷:Java語(yǔ)言使用最多的語(yǔ)法糖。
條件編譯:根據(jù)布爾常量值的真假,編譯器將會(huì)把分支中不成立的代碼塊消除掉,由于這種條件編譯的實(shí)現(xiàn)方式使用了if語(yǔ)句,所以只能寫在方法體內(nèi)部,只能實(shí)現(xiàn)語(yǔ)句基本塊級(jí)別的條件編譯。
第十一章1.HotSpot虛擬機(jī)內(nèi)的即時(shí)編譯器
解釋器與執(zhí)行器:
當(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(C1和C2).目前主流的HotSpot虛擬機(jī)中,默認(rèn)采用解釋器與其中一個(gè)編譯器直接配合的方式工作。為了在程序啟動(dòng)響應(yīng)速度與運(yùn)行效率之間達(dá)到最佳平衡,HotSpot虛擬機(jī)會(huì)逐漸啟用分層編譯的策略。
編譯對(duì)象與觸發(fā)條件:
在運(yùn)行過程中會(huì)被即時(shí)編譯器編譯的熱點(diǎn)代碼有兩類:被多次調(diào)用的方法;被多次執(zhí)行的循環(huán)體。這兩種情況編譯器都會(huì)以整個(gè)方法作為編譯對(duì)象。
判斷是否是熱點(diǎn)代碼和是否需要觸發(fā)即時(shí)編譯的行為稱為熱點(diǎn)探測(cè),方式分為:基于采樣的熱點(diǎn)探測(cè)、基于計(jì)數(shù)的熱點(diǎn)探測(cè)。
編譯過程:
Server Complier和Client Complier兩個(gè)編譯器編譯過程不一樣。Client是一個(gè)簡(jiǎn)單快速的三段式編譯器,主要關(guān)注局部性的優(yōu)化,Server是一個(gè)充分優(yōu)化過的高級(jí)編譯器。
2.編譯優(yōu)化技術(shù)
方法內(nèi)聯(lián):去除方法調(diào)用的成本(建立棧幀等);可以便于在更大范圍內(nèi)采取后續(xù)的優(yōu)化手段。
冗余訪問消除:
復(fù)寫傳播
無(wú)用代碼消除
公共子表達(dá)式消除:如果表達(dá)式E已計(jì)算過,且E中所有變量值未發(fā)生變化,那就直接用已計(jì)算過的代替
數(shù)組范圍檢查消除:如果編譯器只要通過數(shù)據(jù)流分析就可以判定循環(huán)變量的取值范圍在區(qū)間內(nèi),那在整個(gè)循環(huán)中可以把數(shù)組的上下界檢查消除,可以節(jié)省很多次的條件判斷操作
逃逸分析:分析對(duì)象動(dòng)態(tài)作用域:當(dāng)一個(gè)對(duì)象在方法中被定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到方法中,稱為方法逃逸,還有可能被外部線程訪問到,稱為線程逃逸。如果確認(rèn)無(wú)法逃逸,可以進(jìn)行一些高效優(yōu)化:棧上分配、同步消除、標(biāo)量替換。
第十二章1.Java內(nèi)存模型
Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中(虛擬機(jī)內(nèi)存一部分)。每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程使用到的變量的主內(nèi)存的副本拷貝,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。不同的線程之間也無(wú)法直接訪問對(duì)方工作內(nèi)存中的變量,線程變量值的傳遞均需要通過主內(nèi)存來(lái)完成。
內(nèi)存交互:lock、unlock、read、load、use、assign、store、write。
volatile:
volatile變量?jī)煞N特性:第一是保證此變量對(duì)所有線程的可見性,可見性是指當(dāng)一條線程修改了這個(gè)變量的值,新值對(duì)于其他線程立即可知。第二是禁止指令重排序優(yōu)化。
volatile使用場(chǎng)景:運(yùn)算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值;變量不需要與其他的狀態(tài)變量共同參與不變約束。
原子性、可見性、有序性
先行發(fā)生原則:先行發(fā)生是Java內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系,如果說(shuō)操作A先行發(fā)生于操作B,其實(shí)就是在發(fā)生操作B之前,操作A產(chǎn)生的影響能被操作B觀察到:程序次序規(guī)則、監(jiān)視器鎖規(guī)則、volatile變量規(guī)則。時(shí)間先后順序與先行發(fā)生原則之間基本沒有太大關(guān)系。
2.Java與線程
實(shí)現(xiàn)線程主要有3種方式:使用內(nèi)核線程實(shí)現(xiàn)、使用用戶線程實(shí)現(xiàn)和使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)。Java采用一對(duì)一線程模型。
Java線程調(diào)度:線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過程,分別是協(xié)同式線程調(diào)度和搶占式線程調(diào)度。
線程狀態(tài):新建、運(yùn)行、無(wú)限期等待、限期等待、阻塞、結(jié)束。
第十三章1.線程安全
Java中各種操作共享的數(shù)據(jù)分為以下5類:不可變、絕對(duì)線程安全、相對(duì)線程安全、線程兼容、線程對(duì)立。
線程安全的實(shí)現(xiàn)方法:互斥同步、非阻塞同步、無(wú)同步方案。
2.鎖優(yōu)化
自旋鎖和自適應(yīng)自旋、鎖消除、鎖粗化、輕量級(jí)鎖、偏向鎖。
第五章、第九章、第十章的插入式注解處理器等實(shí)例分析需要重點(diǎn)看一下。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/68707.html
摘要:抽時(shí)間重新讀了一遍深入理解一書。驗(yàn)證確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全??梢娦钥梢娦允侵府?dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。 抽時(shí)間重新讀了一遍《深入理解JVM》一書。以下為摘錄內(nèi)容。 1 java內(nèi)存區(qū)域 showImg(https://segmentfault.com/img/bVboDgk?w=617&h=365...
摘要:年開始工作,年畢業(yè),兩年來(lái)的工作接觸知識(shí)面很廣,用的東西比較多,包括基礎(chǔ)的開發(fā)到開發(fā)到大數(shù)據(jù),推薦系統(tǒng),到服務(wù)器運(yùn)維,到數(shù)據(jù)庫(kù)維護(hù),,,可愈發(fā)明白貪多嚼不爛的道理,唯有才能踏踏實(shí)實(shí),趁著剛剛讀完這本書,想復(fù)習(xí),順便寫一些筆記,聊以鞏固。 13年開始工作,14年畢業(yè),兩年來(lái)的工作接觸知識(shí)面很廣,用的東西比較多,包括基礎(chǔ)的java開發(fā)到j(luò)2ee,web開發(fā),到大數(shù)據(jù),推薦系統(tǒng),到服務(wù)器運(yùn)維...
摘要:監(jiān)控和故障處理工具顯示指定系統(tǒng)內(nèi)所有的虛擬機(jī)進(jìn)程用于收集虛擬機(jī)各方面的運(yùn)行數(shù)據(jù)。的常用功能選項(xiàng)測(cè)試上面輸出了我正在運(yùn)行程序的包名下的類名虛擬機(jī)統(tǒng)計(jì)信息監(jiān)視工具使用于監(jiān)視虛擬機(jī)各種運(yùn)行狀態(tài)信息的命令行工具。 《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第二版》讀書筆記與常見面試題總結(jié) 本節(jié)常見面試題(推薦帶著問題閱讀,問題答案在文中都有提到): JVM調(diào)優(yōu)的常見命令行工具有哪些?...
摘要:大多數(shù)待遇豐厚的開發(fā)職位都要求開發(fā)者精通多線程技術(shù)并且有豐富的程序開發(fā)調(diào)試優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問題在面試中經(jīng)常會(huì)被提到。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 JVM 內(nèi)存溢出實(shí)例 - 實(shí)戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細(xì)介紹 Java 注解的使用,有利于學(xué)習(xí)編譯時(shí)注解 Java 程序員快速上手 Kot...
閱讀 916·2021-11-18 10:02
閱讀 2683·2021-11-11 16:54
閱讀 2852·2021-09-02 09:45
閱讀 714·2019-08-30 12:52
閱讀 2902·2019-08-29 14:04
閱讀 2798·2019-08-29 12:39
閱讀 522·2019-08-29 12:27
閱讀 1955·2019-08-26 13:23