JVM 前半部分知識點(diǎn)(HotSpot jvm)

第一部分:自動(dòng)內(nèi)存管理

總圖:

1.jpg
部分名詞解釋:
  • slot(槽):指棧中存放局部變量的容器臊恋。double怖侦、long占2個(gè)slot教藻,其他占1個(gè)slot距帅。注意:并不用指定一個(gè)槽多大。因?yàn)榫植孔兞康拇笮《际谴_定的括堤。

  • TLAB:Thread Local Allocation Buffer碌秸,線程私有的分配緩沖區(qū)。

  • 直接內(nèi)存:Direct Memory悄窃,其類似于真實(shí)物理內(nèi)存讥电,是不屬于上述運(yùn)行時(shí)數(shù)據(jù)區(qū)的內(nèi)存分區(qū)。保存了對象的實(shí)現(xiàn)轧抗,而對象的引用可以放在堆內(nèi)恩敌,如此可提高性能。

    需要注意配置java堆時(shí)(有的jvm必須有最大限制横媚,且有默認(rèn)值)纠炮,要考慮上直接內(nèi)存,否則也會Out Of Memory

  • OOP:Object Oriented Programming灯蝴,面向?qū)ο缶幊?/p>

  • Oop: Ordinary Object Pointer恢口,普通對象指針

  • JMC(Java Mission Control):java任務(wù)控制

  • JFR(Java Flight Recorder):

  • 全限定名:名字里包含了目錄,用作接口穷躁、類耕肩。相對應(yīng)的是簡單名,只是方法或字段的名字。

參數(shù)

  • -verbose:gc ——打開gc的跟蹤日志

  • -XX:+printGC——打開GC的log的開關(guān)猿诸,簡要日志

  • -XX:+PrintGCDetails:打印GC的詳細(xì)信息

  • -XX:+TraceClassLoading(監(jiān)控類加載婚被,可以在程序運(yùn)行時(shí)檢出哪些類被加載了

  • -XX:+PrintClassHistogram(加入此參數(shù),在運(yùn)行時(shí)不會有其他東西輸出两芳,但是在按下Ctrl+Break后可以打印出類的信息摔寨,類的直方圖)

  • -Xmx(最大堆的空間)

  • -Xms(最小堆的空間)

  • -Xmn (設(shè)置新生代的大小)

  • -XX:NewRatio(設(shè)置新生代和老年代的比值,如果設(shè)置為4則表示(eden+from(或者叫s0)+to(或者叫s1)): 老年代 =1:4)怖辆,即年輕代占堆的五分之一

  • -XX:SurvivorRatio(設(shè)置兩個(gè)Survivor(幸存區(qū)from和to或者叫s0或者s1區(qū))和eden區(qū)的比)是复,8表示兩個(gè)Survivor:eden=2:8,即Survivor區(qū)占年輕代的五分之一

  • -XX:+HeapDumpOnOutOfMemoryError(將OOM時(shí)的堆信息導(dǎo)出到文件)
    如果系統(tǒng)出現(xiàn)OOM一般情況系統(tǒng)有可能會down掉竖螃,但是我們排查問題時(shí)需要場景重現(xiàn)是比較困難的淑廊,所以當(dāng)我們輸出了OOM的異常時(shí),就可以直接查看特咆,找出導(dǎo)致OOM的原因

  • -XX:+HeapDumpPath=XXXX(導(dǎo)出OOM堆信息文件的路徑)

  • -XX:OnOutOfMemoryError(在系統(tǒng)出現(xiàn)OOM時(shí)季惩,執(zhí)行一個(gè)腳本,可以發(fā)送郵件腻格,報(bào)警或者是重啟程序)

  • -XX:PermSize(設(shè)置永久代的初始空間大谢啊)

  • -XX:MaxParmSize(設(shè)置永久代的最大空間)

  • -Xss(設(shè)置棧空間的大胁酥啊)

可能問題及原因:
  • StackOverFlowError

    當(dāng)線程調(diào)用的棧深度超過jvm所允許也會報(bào)青抛。(虛擬機(jī)內(nèi)存容量不夠)

    或者棧幀太大(一個(gè)方法內(nèi)部的變量太多)時(shí),無法申請足夠的空間也會報(bào)酬核。

  • OutOfMemory

    某些jvm容量是可以動(dòng)態(tài)擴(kuò)展的蜜另。拓展無法申請到足夠內(nèi)存時(shí),會報(bào)出此錯(cuò)誤嫡意。(HotSpot虛擬機(jī)不允許棧動(dòng)態(tài)擴(kuò)展举瑰,所以不會出現(xiàn)這種原因的報(bào)錯(cuò),但申請失敗時(shí)也會報(bào)這個(gè)錯(cuò)誤蔬螟。)

    對于來說此迅,一般都是可拓展的,當(dāng)有線程申請分配旧巾,但其內(nèi)部空間不夠耸序,而且堆也無法拓展時(shí),會報(bào)錯(cuò)菠齿。

    對于方法區(qū)佑吝,當(dāng)其無法提供新的內(nèi)存分配需求時(shí)坐昙,會報(bào)錯(cuò)绳匀。如運(yùn)行時(shí)添加的常量,也會向方法區(qū)申請內(nèi)存,但如果方法區(qū)內(nèi)存已滿疾棵,且無法拓展時(shí)戈钢,變會報(bào)內(nèi)存溢出。


java堆

部分名詞解釋:

  • 指針碰撞:Bump The Pointer是尔,描述java堆分配新內(nèi)存時(shí)的動(dòng)作殉了。

1.對象的創(chuàng)建

結(jié)構(gòu):

  • 對象頭(header):大小為一個(gè)Mark Word,32or64位(同于系統(tǒng))拟枚。包含:對象哈希嗎薪铜、對象分帶年齡、存儲鎖標(biāo)志位恩溅。(還可能有:類型指針隔箍,指向類的元數(shù)據(jù)(java方法區(qū)的常量池中);java數(shù)組header會有一個(gè)總大小header字段脚乡、以此判斷該數(shù)組對象大醒烟病)

  • // Bit-format of an object header (most significant first, big endian layout belo//
    // 32 bits: // -------- // hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
    // JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2       (biased object) 
    // size:32 ------------------------------------------>|      (CMS free block) 
    // PromotedObject*:29 ---------->| promobits:3  ----->|      (CMS promoted object)
    
  • 實(shí)例數(shù)據(jù)(Instance Data):包含從父類繼承的和本類的數(shù)據(jù)。排序方式默認(rèn)為從大到小奶稠,即:long\double俯艰、int、short...還有一些其他設(shè)定锌订,如compactFields竹握,緊湊字段設(shè)置,可以將小數(shù)據(jù)插入其他數(shù)據(jù)中間瀑志。

    所以順序和代碼里面的順序可能不同涩搓。

  • 對象填充(Padding):hospot要求每個(gè)對象都是8字節(jié)對齊。所以在實(shí)例數(shù)據(jù)尾部可能會有填充劈猪。(對象頭已是8字節(jié)對齊)


java棧

StackOverflowError內(nèi)存泄漏原因:(如下方法是hotspot jvm中昧甘,其不可拓展棧幀長度,并設(shè)置了棧幀最大值)

①棧深度過深:線程(方法)的棧深度超過jvm限制許可战得,如我測試時(shí)1050左右便不允許再深入充边。

②棧幀太大/jvm容量不夠:一個(gè)方法內(nèi)的變量太多,導(dǎo)致雖然沒有達(dá)到棧深度限制常侦,但是無法申請足夠的內(nèi)存浇冰。

說明:堆棧溢出,說明可能是其本身的問題聋亡,并非物理內(nèi)存不夠肘习。和OutOfMemory,內(nèi)存溢出不相同坡倔。

OutOfMemory內(nèi)存溢出原因:(尤其是在32位系統(tǒng)應(yīng)用開發(fā)時(shí)漂佩,更應(yīng)該注意脖含。)

①線程過多:線程創(chuàng)建過多,也類似棧溢出中②的問題投蝉,并可能導(dǎo)致操作系統(tǒng)假死养葵。

②jvm容量不夠/物理內(nèi)存也不夠:當(dāng)棧可以拓展時(shí)瘩缆,可能不會出現(xiàn)棧溢出关拒,而是會出現(xiàn)內(nèi)存溢出的問題。java.lang.OutOfMemoryError: unable to create native thread


方法區(qū)(Methods Area)

說明:主要職責(zé)在于存放class的信息:如類名庸娱、訪問修飾符着绊、常量池、字段描述熟尉、方法描述等

OutOfMemory內(nèi)存溢出原因

①(jdk6及以前)常量池容量不夠:可以通過限制常量池大小 -XX:PermSize=6M -XX:MaxPermSize=6M等來限制畔柔,并一直向常量池添加數(shù)據(jù)(可以通過String::intern()來進(jìn)行),會出現(xiàn)這個(gè)報(bào)錯(cuò)臣樱。但在jdk7以后靶擦,永久代漸漸取消、jdk8之后永久代完全放棄雇毫,不能得到常量池溢出(會出現(xiàn)堆溢出)玄捕,因?yàn)榉旁谟谰么淖址A砍貜姆椒▍^(qū)轉(zhuǎn)移到了堆中。

CGLib開源項(xiàng)目:http://cglib.sourceforge.net/棚放。


垃圾回收(Garbage Collection)

圖示:hotspot的分代垃圾收集器枚粘。線相連代表可以一起配合使用。(沒有最好飘蚯,只有適合)下面的組合上有jdk9的馍迄,代表已取消支持搭配。(CMS局骤,Concurrent Mark Sweep攀圈,也被稱為并發(fā)低停頓收集器,并行標(biāo)記掃描峦甩;G1赘来,Garbage First,其可處理整個(gè)堆中的區(qū)域凯傲,是收集器技術(shù)發(fā)展的里程碑犬辰;)

parallel 并行:指多條垃圾收集器線程之間的關(guān)系,同時(shí)有多條線程在協(xié)同工作冰单; 主要用于服務(wù)器后臺幌缝,提升吞吐量(運(yùn)行用戶代碼時(shí)間 /(運(yùn)行用戶代碼時(shí)間+運(yùn)行收集器線程時(shí)間))。Parallel Scavenge還有一個(gè)名稱“吞吐量優(yōu)先收集器”(配合Parallel Old)

Concurrent 并發(fā):指垃圾收集器線程和用戶線程之間的關(guān)系诫欠,說明垃圾收集器線程和用戶線程同時(shí)運(yùn)行涵卵。如CMS等收集器主要用于客戶端腿宰,減少用戶線程停頓時(shí)間,提升服務(wù)質(zhì)量和交互能力缘厢。

Shenandoah:是其他公司開發(fā)的一個(gè)收集器,其目標(biāo)是low-pause低延遲甩挫,并且確實(shí)做得好贴硫。其也是全堆通用,因?yàn)槠洳]有進(jìn)行分代伊者,而是想G1將內(nèi)存劃分成多個(gè)region英遭。

三大指標(biāo):內(nèi)存占用(footprint)、吞吐量(時(shí)間比)亦渗、延遲(今后最被重視的指標(biāo))

other:https://blogs.oracle.com/jonthecollector/our_collectors挖诸。牽掛你的人

2.jpg
3.jpg

Parallel Scavenge:目標(biāo)是為了獲得最大吞吐量(上述定義),多用于服務(wù)器使用法精。

CMS:目標(biāo)是為了獲得最短暫停時(shí)間多律,多用于客戶端或互聯(lián)網(wǎng)服務(wù)器,為了提供短暫的系統(tǒng)停頓時(shí)間搂蜓,提高客戶服務(wù)狼荞。

說明:主要工作解決的問題是:①如何定位應(yīng)被刪除對象;②何時(shí)刪除對象帮碰;③如何刪除對象相味。

GC Roots:

·在虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象,譬如各個(gè)線程被調(diào)用的方法堆棧中使用到的

參數(shù)殉挽、局部變量丰涉、臨時(shí)變量等。

·在方法區(qū)中類靜態(tài)屬性引用的對象斯碌,譬如Java類的引用類型靜態(tài)變量一死。

·在方法區(qū)中常量引用的對象,譬如字符串常量池(String Table)里的引用傻唾≌·在本地方法棧中JNI(即通常所說的Native方法)引用的對象。

·Java虛擬機(jī)內(nèi)部的引用策吠,如基本數(shù)據(jù)類型對應(yīng)的Class對象逛裤,一些常駐的異常對象(比如

NullPointExcepiton、OutOfMemoryError)等猴抹,還有系統(tǒng)類加載器带族。

·所有被同步鎖(synchronized關(guān)鍵字)持有的對象。

·反映Java虛擬機(jī)內(nèi)部情況的JMXBean蟀给、JVMTI中注冊的回調(diào)蝙砌、本地代碼緩存等阳堕。

垃圾回收算法:Richard Jones撰寫的《垃圾回收算法手冊》

垃圾收集算法可以劃分為“引用計(jì)數(shù)式垃圾收集”(Reference Counting GC)和“追蹤式垃圾收集”(Tracing GC)兩大類

分代收集(generational Collection):

說明:其理論建立在兩個(gè)假說上面:強(qiáng)分代假說(Strong Generational Hypothesis)、弱分代假說(Weak Generational Hypothesis)择克。

逃過一次GC恬总,年齡就加一,逃過越多次GC的對象肚邢,就越難以消除映胁。而絕大多數(shù)對象都是朝生夕死的京痢,以此將堆空間分出兩個(gè)區(qū),強(qiáng)分代的GC頻率低、弱分代GC頻率高苍在。

以此中和內(nèi)存空間利用率和GC算法性能(時(shí)間)消耗掂骏。由分代收集劃分出不同內(nèi)存區(qū)域的思想焕毫,其后延伸出很多算法和劃分方式拉岁。

內(nèi)存劃分如:新生代(young generation)、老年代(tenured generation)等浦夷,并因其可能會互相引用辖试、而延伸出第三個(gè)假說:跨代引用假說(Intergerenational preference hypothesis)。

跨帶引用假說:由于將所有老年代作為GC Roots比較耗性能劈狐,故在新生代內(nèi)存區(qū)域建立一個(gè)記憶集(remembered set)全局?jǐn)?shù)據(jù)結(jié)構(gòu)剃执,將老年代分為小塊:有對新生代的引用,和沒有的懈息。minor GC掃描時(shí)只需要將有引用那塊的老年代作為GC Roots即可肾档。

GC劃分如:Partial GC:{ Minor GC/ Young GC、Major GC / Old GC辫继、Mixed GC } 怒见、 Full GC;

如:minor GC只會對新生代進(jìn)行掃描姑宽;Major GC只針對老年代遣耍,但一般的jvm很少會單獨(dú)手機(jī)老年代,只有G1這樣做炮车,并且Major在不同jvm中舵变,定義可能不同。

收集器清除算法:

基礎(chǔ)算法是1960 Lisp之父提出的Mark-Sweep 標(biāo)記-清除算法瘦穆。其后很多算法都是以其為基礎(chǔ)纪隙,進(jìn)行改進(jìn)得到。

  • 標(biāo)記-清除算法:全部進(jìn)行標(biāo)記(可以標(biāo)記存活或回收的)扛或,然后一次清除绵咱。

  • 標(biāo)記-復(fù)制算法:通過將內(nèi)存區(qū)域劃分為兩個(gè)區(qū):使用區(qū)和空閑區(qū)。每次垃圾清除熙兔,將存活的對象復(fù)制到空閑區(qū)悲伶,然后清除使用區(qū)艾恼,并交換兩者區(qū)域?qū)傩浴?989年Andrew Appel針對具備“朝生夕滅”特點(diǎn)的對象,提出了一種更優(yōu)化的半?yún)^(qū)復(fù)制分代策略麸锉,現(xiàn)在稱為“Appel式回收”钠绍。

  • Appel式回收:將新生代分為1個(gè)eden(伊甸園)和2個(gè)survivor,一次保留一個(gè)survivor不使用花沉。如hotspot中eden和survivor比例為8:1柳爽,所以一次使用(8+1)/10=90%的內(nèi)存區(qū)域,不算太浪費(fèi)主穗。如果survivor不夠時(shí),會分配到老年代中毙芜,并在eden清空后分配回來忽媒。標(biāo)記-復(fù)制算法和appel,其理論依據(jù)都是在于新生代對象存活率很低的情況下腋粥, 而這一般復(fù)合現(xiàn)實(shí)的規(guī)律晦雨。但老年代的存活率很高,其復(fù)制開銷也會很大隘冲,不能用這種算法闹瞧。

  • 標(biāo)記-整理算法(Mark-Compact):(compact,緊湊的展辞,v壓縮)多用于老年代奥邮,和標(biāo)記-清除算法很像,也是先標(biāo)記罗珍,然后清除洽腺,但是其清除后,會對老年代存活對象進(jìn)行移動(dòng)覆旱,使之緊湊蘸朋,解決了內(nèi)存碎片化問題,但由于要移動(dòng)和更改引用扣唱,其間會暫停線程蠻長時(shí)間(標(biāo)記-清除也會藕坯,但很短),延遲還是蠻高的噪沙。(hotspot 的Parallel Scavenge收集器)

  • 混合算法:對于老年代炼彪,可以先采取標(biāo)記-清除算法,直到內(nèi)存碎片已經(jīng)影響內(nèi)存分配正歼,進(jìn)行一次標(biāo)記-清理算法霹购,hotspot的cms算法就是這樣做的。

并發(fā):當(dāng)收集器和其他線程并發(fā)時(shí)朋腋,要避免出現(xiàn)”對象消失“問題:以三色來理解:{ 黑:代表其已被掃描齐疙,且所引用的對象都已被掃描膜楷。灰:代表自身已被掃描贞奋,但其所引用對象還沒有(執(zhí)行ing)赌厅;白:代表沒有被掃描 ;見https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking轿塔。}

當(dāng)并行時(shí)特愿,可能出現(xiàn)灰色對象取消白色對象的引用,但之前的黑色對象卻又引用了白色對象勾缭,此時(shí)白色對象會被錯(cuò)誤編入待清除對象中揍障。我們要解決這個(gè)問題。

“對象消失”問題出現(xiàn)的兩個(gè)必要:

  • 賦值器插入了一條或多條從黑色對象到白色對象的新引用俩由; (解決方案:增量更新毒嫡;掃描完后再掃描中途有引用其他對象行為的黑色節(jié)點(diǎn)。cms收集器)

  • 賦值器刪除了全部從灰色對象到該白色對象的直接或間接引用幻梯。(解決方案: 原始快照兜畸;中途有刪除引用關(guān)系的,不直接刪除碘梢,而是以開始時(shí)的快照進(jìn)行咬摇,結(jié)束后再進(jìn)行掃描。G1煞躬、Shenandoah收集器)

我們只要中斷一個(gè)就可以肛鹏。

計(jì)數(shù)收集:


java工具

...java/bin/里面,有很多命令行工具恩沛,具體看深入理解java虛擬機(jī) 4.2.6龄坪,

還有http://openjdk.java.net/jeps/320

還有一些可視化工具复唤、虛擬機(jī)插件及工作健田。(JHSDB、JConsole佛纫、Visual VM妓局、BTrace(visual VM插件,也可以獨(dú)立)呈宇、JFR好爬、JMC、HSDIS用于輸出匯編代碼和JITWacth搭配)



class文件

用hex來查看class文件甥啄,用命令行工具javap來翻譯class文件:javap -verbose filePath存炮。

  • 部分名詞解釋:
    • 類型后綴_info:類型分為無符號類型和表。無符號用u1、u2穆桂、u4宫盔、u8表示,代碼字節(jié)個(gè)數(shù)享完;表用info表示灼芭,里面可能有多個(gè)無符號和表數(shù)據(jù)類型;

格式:固定順序的:

①(u4-四字節(jié))magic number:標(biāo)明這個(gè)文件的格式般又。如class文件是:xCAFEBABE

②(u4)minor version(次版本號=2bytes)和majoy version(主版本號=2bytes):主版本號從45開始彼绷,次版本號為0~65535.如jdk1 majoy version =45、jdk13 majoy version=57茴迁。

③(u2-兩字節(jié))constant pool count(常量池里面的數(shù)量)寄悯。

④(中間省略所有常量池的常量....)常量池里面的數(shù)據(jù)共有十七中結(jié)構(gòu)(到j(luò)dk12)幾乎都不同,但第一項(xiàng)相同堕义,是標(biāo)簽tag猜旬,如下表所示,共有17種胳螟。具體常量池的結(jié)構(gòu)看:常量池內(nèi)容

4.jpg

⑤(u2)Access tag(訪問標(biāo)志):共有16個(gè)標(biāo)志位可用昔馋,但當(dāng)前只有9個(gè)被定義了(每個(gè)標(biāo)志位用二進(jìn)制0/1來標(biāo)志)筹吐。主要用作對方法的標(biāo)識糖耸,如其是否是普通類還是abstract、接口類丘薛,or其是否是public等嘉竟。

⑥(u23)this_class* (類索引)、super_class(父類索引)洋侨、interfaces(接口索引):每個(gè)2字節(jié)舍扰,索引的目的地為常量池對象列表,如0x0001希坚,指常量池中第一個(gè)對象边苹,也就是上述常量池中第一個(gè)常量數(shù)據(jù)。

⑦( u2)field count:字段表集合數(shù)量裁僧。

⑧(每字段4對u2+可拓展的屬性表):標(biāo)識該類里面的字段表數(shù)據(jù)个束,可能有多個(gè)字段。每一個(gè)字段表數(shù)據(jù)有如下結(jié)構(gòu):

access_flags(u2聊疲、類似于上述訪問標(biāo)志茬底,標(biāo)識該字段的修飾符等類型,如是否是public等)+

name_index(u2获洲、常量池索引) +

descriptor_index(u2阱表、常量池索引):{ 結(jié)構(gòu):參數(shù)列表"(...)" + 返回值:int fun(int x, char []b) = (I[C)I };

attribute_count(u2):表示屬性表的數(shù)量。

后面還有可能附加attribute_info表最爬,里面是附加信息涉馁。如:int x=30;給定的初始值會作為常量池中的常量烂叔,而附加屬性對其引用谨胞。

8.jpg

⑨(u2)方法區(qū)數(shù)量。

⑩(4對u2+屬性表集合)方法區(qū):屬性表集合擁有很多屬性_info蒜鸡,具體看下文


常量池內(nèi)容

5.jpg
6.jpg
7.jpg

方法表

和字段表很類似胯努。其屬性表中會有code

結(jié)構(gòu)為:4個(gè)u2+方法的屬性表:(順序)access_flag、name_index逢防、descriptor_index(說明參數(shù)和返回值)叶沛、屬性表個(gè)數(shù)、n個(gè)具體屬性表(如Code表忘朝,下面有詳述)


屬性表

屬性表通用格式:

11.jpg

屬性表集合:

9.jpg
10.jpg

Code屬性表

12.jpg

max_stack:最大棧深度灰署,虛擬機(jī)運(yùn)行時(shí)根據(jù)這個(gè)值來分配棧幀中操作棧深度。

max_locals:最大局部變量的值局嘁,定義槽slot的數(shù)量溉箕,一個(gè)槽可以放32位,但只能放一個(gè)變量悦昵,64位的要兩個(gè)槽肴茄。其所定義的槽數(shù)量,并非所有局部變量的數(shù)量和但指,而是同一時(shí)間寡痰,存活的最大局部變量數(shù)量和類型計(jì)算出max_locals的值。以節(jié)約內(nèi)存棋凳。

code_length:字節(jié)碼的長度拦坠,雖然是u4,但實(shí)際上超過65535(u2)字節(jié)碼就會拒絕編譯剩岳。如jsp內(nèi)面和內(nèi)容贞滨,可能會規(guī)定到一個(gè)方法中,可能會超長編譯失敗拍棕。

code:由length個(gè)u1組成晓铆,每條指令的第一個(gè)字節(jié)u1,類似于處理器指令集莫湘,指出該指令的意義和該指令的長度等尤蒿。《java虛擬機(jī)規(guī)范》定義了越200條編碼值對應(yīng)的指令意義幅垮。詳情附錄C“虛擬機(jī)字節(jié)碼指令表”腰池。

exception_table_length:異常表長度

exception_table:異常表代碼,也是用了指令集


Exception屬性表

說明:與上述Code屬性表中的異常表不同,這個(gè)異常屬性表示弓,列出的是方法拋出的異常讳侨。

格式:

13.jpg

number_of_exceptions :表示異常種類的個(gè)數(shù)。

exception_index_table:索引常量池中的Constant_Class_info型常量奏属,代表該被檢查的異常的類型的名字跨跨。


LineNumberTable屬性表

功能:主要用于將java源碼行號與code字節(jié)碼偏移量進(jìn)行關(guān)聯(lián)映射。方便調(diào)試操作等囱皿,但非必須勇婴。javac中編譯時(shí),可以輸入-g:none嘱腥、-g:lines來取消關(guān)聯(lián)耕渴。

14.jpg

line_number_table:包含多個(gè)line_number_info類型的數(shù)據(jù)。

line_number_info表:包含start_pc和line_number兩個(gè)u2類型的數(shù)據(jù)項(xiàng)齿兔,前者是字節(jié)碼行號橱脸,后者是Java源

碼行號。


LocalVariableTable及LocalVariableTypeTable屬性表

功能:將java源碼局部變量與棧幀中局部變量聯(lián)系起來分苇。當(dāng)別人引用這個(gè)方法時(shí)添诉,參數(shù)名也會存在,如果取消此關(guān)聯(lián)医寿,可能外部引用時(shí)栏赴,局部變量名字會丟失,用args0糟红、args1代替艾帐,雖然不影響運(yùn)行乌叶,但不方便調(diào)試盆偿;編譯時(shí)可用javac -g:none或 -g:vars來關(guān)閉。

格式:

15.jpg

name_index和description_index:都是對常量池進(jìn)行索引准浴,得到變量名事扭,參數(shù)和返回類型。

index:是變量槽中的偏移量乐横。如果是64位求橄,其對應(yīng)的值時(shí)index和index+1。

LocalVariableTypeTable:這個(gè)新增的屬性結(jié)構(gòu)與LocalVariableTable非常相似葡公,僅僅是把記錄的字段描述

符的descriptor_index替換成了字段的特征簽名(Signature)罐农。對于非泛型類型來說,描述符和特征簽名

能描述的信息是能吻合一致的催什,但是泛型引入之后涵亏,由于描述符中泛型的參數(shù)化類型被擦除掉[3],描

述符就不能準(zhǔn)確描述泛型類型了。因此出現(xiàn)了LocalVariableTypeTable屬性气筋,使用字段的特征簽名來完

成泛型的描述拆内。


SourceFile、SourceDebugExtension屬性表

功能:生成.class文件的源文件名宠默,一般類名和源文件名都是一樣的麸恍。

關(guān)閉:-g: none \ -g:source

格式:(u2)屬性名索引、(u4)屬性長度搀矫、(u2)常量池索引(得到.java源文件名)

SoureceDebugExtension:為了方便在編譯器和動(dòng)態(tài)生成的Class中加入供程序員使用的自定義內(nèi)容抹沪,在JDK 5時(shí),新增了 SourceDebugExtension屬性用于存儲額外的代碼調(diào)試信息瓤球。典型的場景是在進(jìn)行JSP文件調(diào)試時(shí)采够,無法通過Java堆棧來定位到JSP文件的行號。JSR 45提案為這些非Java語言編寫冰垄,卻需要編譯成字節(jié)碼并運(yùn)行在Java虛擬機(jī)中的程序提供了一個(gè)進(jìn)行調(diào)試的標(biāo)準(zhǔn)機(jī)制蹬癌,使用SourceDebugExtension屬性就可以用于存儲這個(gè)標(biāo)準(zhǔn)所新加入的調(diào)試信息,譬如讓程序員能夠快速從異常堆棧中定位出原始JSP中出現(xiàn)問題的行號虹茶。

格式:

16.jpg

ConstantValue 屬性表


innerClasses 屬性表

說明:一個(gè)類包含了內(nèi)部類逝薪,則會生成該屬性表,用于記錄內(nèi)部類和宿主類之間的關(guān)聯(lián)蝴罪。

[圖片上傳失敗...(image-782504-1594430035540)]

inner_class_info_index和outer_class_info_index:分別代表內(nèi)部類和宿主類在常量池中的類型為CONSTANT_Class_info符號引用董济。

inner_name_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表這個(gè)內(nèi)部類的名稱要门,如果是匿名內(nèi)部類虏肾,這項(xiàng)值為0。

inner_class_access_flags:內(nèi)部類的訪問標(biāo)志欢搜,類似于類的access_flags封豪,它的取值范圍如表6-26所示。


Deprecated炒瘟、Synthetic屬性表

Deprecated:(棄用的)通過在方法吹埠、字段前添加@Deprecated來設(shè)置。編譯時(shí)疮装,會在屬性表中進(jìn)行簡單描述缘琅。

SYnthetic:(人造的)標(biāo)識該方法、字段廓推、類是編譯器自動(dòng)生成的刷袍,非從源代碼中來。

格式:

(u2)屬性名常量池索引 + (u4)屬性長度(恒定為0x00000000)樊展。


StackMapTable

18.jpg

Signature屬性表

19.jpg

BootStrapMethods屬性表

20.jpg
21.jpg

MethodParameters屬性表

22.jpg

還有兩個(gè)屬性沒有寫


字節(jié)碼指令簡介

說明:java虛擬機(jī)采用的是面向操作數(shù)棧而非寄存器的架構(gòu)呻纹。

詳情:Java虛擬機(jī)規(guī)范(Java SE 7)——第六章鸽心。

名詞解釋:

  • Opcode:操作碼。1字節(jié)大芯优顽频;用于標(biāo)識特定操作。其后跟隨0~多個(gè)該操作需要的參數(shù)太闺。

  • Operand:操作數(shù)糯景。跟在操作碼后面的數(shù)據(jù)。

  • 助記符:操作碼的助記符省骂,用來簡述操作的意義蟀淮。其中特殊字符用作表明服務(wù)的數(shù)據(jù)類型。l代表long钞澳,s代表short怠惶,b代表byte,c代表char轧粟,f代表float策治,d代表double,a代表reference兰吟。也有的沒有特殊字符通惫。

說明:限于一字節(jié)大小的指令集,對于類型大小<int的混蔼,如byte履腋、char悲雳、short等评也,編譯時(shí)都會進(jìn)行拓展成int來操作。包括數(shù)組如char數(shù)組都會拓展成int數(shù)組來操作翁涤。


加載和存儲指令

功能:用于將數(shù)據(jù)晚吞,在棧幀中的{局部變量表}延旧、{操作數(shù)棧}之間相互傳輸。

  • 從局部變量加載到操作棧:iload载矿、iload_<n>垄潮、fload烹卒、 fload _<n>闷盔、dload、dload _<n> 旅急、aload逢勾、aload _<n>

  • 從操作棧存儲到局部變量:istore、istore_<n>等

  • 將常量加載到操作數(shù)棧:bipush藐吮、sipusldc溺拱、ldc_w逃贝、ldc2_w、aconst_null迫摔、iconst_m1沐扳、

    iconst_ <i>、lconst _<l>句占、fconst _<f>沪摄、dconst _<d> 。

  • 拓展局部變量表的訪問索引:wide

  • <n>:上述如iload_<n>指令中的 _<n>代表了一組指令纱烘。算是iload的特殊形式杨拐。


運(yùn)算指令

說明:對byte、short擂啥、char哄陶、boolean類型的算術(shù)指令,用int類型的指令來代替哺壶∥荻郑《java虛擬機(jī)規(guī)范》指出, 只有xdiv 山宾、xrem 中出現(xiàn)余數(shù)為0時(shí)离赫,會拋出ArithmeticException異常。

算術(shù)指令:用x代替(i塌碌、l渊胸、f、d)台妆;iadd翎猛、ladd、fadd接剩、dadd切厘;xsub;xmul懊缺;xdiv疫稿;

xrem(取反);xshl鹃两、xshr(位移)遗座;ior、lor(按位或)俊扳;iand途蒋、land(按位與);ixor馋记、lxor(按位異或)号坡;iinc(自增)懊烤;·比較指令:dcmpg、dcmpl宽堆、fcmpg腌紧、fcmpl、lcmp畜隶;


類型轉(zhuǎn)換指令

寬化類型轉(zhuǎn)換(Widening Numeric Conversion):從小范圍類型轉(zhuǎn)化為大范圍寄啼。如int -> long/float/double;隱式即可轉(zhuǎn)換代箭。

窄化類型轉(zhuǎn)換(Widening Numeric Conversion):從大范圍類型轉(zhuǎn)化為小范圍墩划。如float/double -> long;必須顯示轉(zhuǎn)換嗡综;

窄化類型轉(zhuǎn)換指令:i2b乙帮、i2c、i2s极景、l2i察净、f2i、f2l盼樟、d2i氢卡、d2l和d2f。


對象創(chuàng)建與訪問指令

創(chuàng)建類的指令:new

創(chuàng)建數(shù)組的指令:newarray晨缴、anewarray译秦、multianewarray

訪問類字段(static字段,也稱類變量)指令:getstatic击碗、putstatic

訪問實(shí)例字段(非static字段筑悴,或稱實(shí)例的變量):getfield、putfield

將數(shù)組元素加載到操作數(shù)棧的指令:baload稍途、caload阁吝、saload、iaload械拍、laload突勇、faload、daload坷虑、aaload甲馋。

操作數(shù)棧的值儲存到數(shù)組元素的指令:bastore’、castore猖吴、sastore摔刁、iastore、fastore海蔽、lastore共屈、fastore、aastore

取數(shù)組長度的指令:arraylength

檢查對象實(shí)例所屬類型的指令:instanceof党窜、checkcast


操作數(shù)棧管理指令

操作數(shù)棧 棧頂出棧:pop拗引、pop2(頂上兩個(gè)元素出棧)

復(fù)制棧頂一個(gè)、二個(gè)數(shù)值幌衣,并將復(fù)制值壓入棧頂:dup矾削、dup2;dup_x1豁护、dup2_x1哼凯;dup2_x2、dup2_x2楚里;

將棧中最頂端的兩個(gè)數(shù)值互換:swap


控制轉(zhuǎn)移指令

說明:有條件或無條件地跳轉(zhuǎn)断部。如之前所說,byte等數(shù)據(jù)類型會轉(zhuǎn)換成int班缎,而long蝴光、float、double則會先進(jìn)行計(jì)算xcmpl达址、xcmpg蔑祟,返回一個(gè)整數(shù)值到操作數(shù)棧中,隨后在執(zhí)行int的條件分支比較操作沉唠。

條件分支:ifeq疆虚、iflt、ifle满葛、ifne装蓬、ifgt、ifge纱扭、ifnull牍帚、ifnonnull、if_icmpgt乳蛾、if_icmple暗赶、if_icompge、if_acmpeq肃叶、if_acmpne

復(fù)合條件分支:tableswitch蹂随、lookupswitch

無條件分支:goto、goto_w因惭、jsr岳锁、jsr_w、ret


方法調(diào)用和返回指令

invokevirtual:用于調(diào)用對象(實(shí)例)的方法蹦魔。

invokeinterface:用于調(diào)用接口的方法激率。

invokespecial:調(diào)用一些需特殊處理的方法咳燕。包括:實(shí)例初始化方法、私有方法乒躺、父類方法等招盲。

invokestatic:調(diào)用類靜態(tài)方法。

invokedynamic:用于在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法嘉冒。

說明:調(diào)用函數(shù)與返回值無關(guān)曹货,但是返回指令是根據(jù)類型進(jìn)行區(qū)分。

ireturn:返回的類型包括int讳推、short顶籽、char、byte银觅、boolean礼饱。

lreturn、freturn设拟、dreturn慨仿、areturn、return(void返回類型)


異常處理指令

athrow:顯式拋出異常纳胧。

java虛擬機(jī)對異常的處理镰吆,不是由字節(jié)碼指令來實(shí)現(xiàn)。而是由異常表來完成跑慕。


同步指令

說明:用monitor(管程万皿,或稱鎖)來實(shí)現(xiàn)方法級同步和方法內(nèi)(一段指令序列)同步『诵校‘

同步:方法執(zhí)行前持有monitor牢硅、然后調(diào)用方法,方法結(jié)束后釋放monitor芝雪。執(zhí)行期間减余,其他線程無法在獲得同一個(gè)monitor管程。當(dāng)發(fā)生異常時(shí)惩系,方法內(nèi)部無法處理并拋出異常到同步方法邊界外后位岔,會釋放管程。

方法級同步:由虛擬機(jī)隱式來完成堡牡。虛擬機(jī)通過訪問該方法常量池里面的ACC_SYNCHRONIZED訪問標(biāo)志是否被設(shè)置抒抬,來決定是否讓該執(zhí)行線程持有管程,隨后再執(zhí)行方法晤柄。

方法內(nèi)同步:當(dāng)java中擦剑,方法內(nèi)部有synchronized語句,則會進(jìn)行同步。

  • monitorenter:調(diào)用前會執(zhí)行該指令

  • monitorexit:調(diào)用結(jié)束前會執(zhí)行該指令惠勒。

    在這兩個(gè)指令必須配對使用赚抡。其中間的指令序列,即是被同步的捉撮。如果沒有異常處理程序怕品,虛擬機(jī)會自動(dòng)生成可處理所有異常的異常處理代碼妇垢,為的是monitorenter能正確配對巾遭。


共有設(shè)計(jì)和私有實(shí)現(xiàn)

如class文件格式和字節(jié)碼指令。兩者與硬件闯估、操作系統(tǒng)灼舍、具體java虛擬機(jī)實(shí)現(xiàn)之間是完全獨(dú)立的。虛擬機(jī)實(shí)現(xiàn)者可以充分優(yōu)化和拓展涨薪,已獲得更好的性能骑素。

實(shí)現(xiàn)方式主要有兩種:

  • 將輸入的java虛擬機(jī)代碼在加載時(shí)或執(zhí)行時(shí),翻譯成另外一種虛擬機(jī)的指令集刚夺。
  • 將輸入的java虛擬機(jī)代碼在加載時(shí)或執(zhí)行時(shí)献丑,翻譯成宿主機(jī)處理程序的本地指令集。(即時(shí)編譯器代碼生成技術(shù))


虛擬機(jī)類加載機(jī)制

類的生命周期:

23.jpg
強(qiáng)制初始化

《java虛擬機(jī)規(guī)范》中對初始化之前的階段是沒有強(qiáng)制要求的侠姑,有且僅有以下六種情況创橄,則必須進(jìn)行初始化:

  • 遇到new、getstatic莽红、putstatic妥畏、invokestaitc

  • 使用java.lang.reflect(反射)包的方法對類型進(jìn)行反射調(diào)用時(shí)。

  • 當(dāng)初始化類的時(shí)候安吁,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化醉蚁,則需要先觸發(fā)其父類的初始化(接口不需要初始化其父類)

  • 當(dāng)虛擬機(jī)啟動(dòng)時(shí),會根據(jù)用戶指定的主類(包含main()方法)鬼店,虛擬機(jī)會先初始化這個(gè)主類网棍。

  • 當(dāng)使用JDK 7新加入的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解

    析結(jié)果為REF_getStatic妇智、REF_putStatic滥玷、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句

    柄俘陷,并且這個(gè)方法句柄對應(yīng)的類沒有進(jìn)行過初始化罗捎,則需要先觸發(fā)其初始化。

  • 當(dāng)一個(gè)接口中定義了JDK 8新加入的默認(rèn)方法(被default關(guān)鍵字修飾的接口方法)時(shí)拉盾,如果有

    這個(gè)接口的實(shí)現(xiàn)類發(fā)生了初始化桨菜,那該接口要在其之前被初始化。

類加載階段

加載

三件事情:

  • 通過一個(gè)類的全限定名,來獲取定義此類的二進(jìn)制字節(jié)流倒得。(泻红?如何獲得?)
  • 將這個(gè)字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)霞掺,轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)谊路。
  • 在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)訪問入口菩彬。

驗(yàn)證

《java虛擬機(jī)規(guī)范》中描述比較籠統(tǒng)缠劝,不是太具體。大概會完成下面四個(gè)階段:

  • 文件格式驗(yàn)證: 驗(yàn)證字節(jié)流是否符合Class文件格式規(guī)范骗灶。如檢測是否以魔數(shù)0xCAFEBABE開頭惨恭、主次版本號是否在可運(yùn)行、常量池的常量中是否有不被支持的常量類型等...內(nèi)容非常龐多耙旦。主要保證輸入的字節(jié)流能正確解析脱羡,并存儲在虛擬機(jī)內(nèi)存方法區(qū)中。(基于二進(jìn)制流免都,之后三個(gè)階段都基于方法區(qū)存儲結(jié)構(gòu))
  • 元數(shù)據(jù)驗(yàn)證:主要對類的元數(shù)據(jù)信息進(jìn)行語義校驗(yàn)(對類本身進(jìn)行校驗(yàn))锉罐,如:是否有父類(除了java.lang.Object之外都應(yīng)有)、父類是否不允許被繼承绕娘、是否實(shí)現(xiàn)了其父類或接口之中要求實(shí)現(xiàn)的所有方法等等...
  • 字節(jié)碼驗(yàn)證:(對類里方法Code屬性進(jìn)行校驗(yàn))最復(fù)雜的一個(gè)驗(yàn)證階段脓规,保證被校驗(yàn)的方法運(yùn)行時(shí)不會危害虛擬機(jī)安全。
  • 符號引用驗(yàn)證:校驗(yàn)類所引用的外部類业舍、方法抖拦、字段等。

當(dāng)確保無誤時(shí)舷暮,可通過-Xverify:none來關(guān)閉态罪,節(jié)約虛擬機(jī)驗(yàn)證時(shí)間。

③準(zhǔn)備

為類中定義的static變量分配內(nèi)存下面,并設(shè)置變量初始值(0复颈,而非代碼中的初始值,如static int x=120沥割,是要到創(chuàng)建實(shí)例才被類構(gòu)造器< clinit>()初始化耗啦;注意:常量除外,常量是直接就初始化為指定值)

④解析

虛擬機(jī)將常量池內(nèi)的符號引用机杜,改成直接引用帜讲。

  • 符號引用:一組字符,描述引用的目標(biāo)椒拗。目標(biāo)并不一定已加載到虛擬機(jī)內(nèi)存中似将。
  • 直接引用:直接指向目標(biāo)的指針获黔、相對偏移量、句柄等在验。目標(biāo)必定已在虛擬機(jī)中了玷氏。

1.類或接口的解析:

  • 非數(shù)組(普通類或接口):虛擬機(jī)把該類型的符號引用(全限定名)提供給類加載器,類加載器加載這個(gè)類到內(nèi)存中腋舌。

  • 數(shù)組:和非數(shù)組差不多盏触,不過其描述符的形式為:"[Ljava/lang/Integer"。

  • 符號引用驗(yàn)證:驗(yàn)證是否本類對引用類有訪問權(quán)限块饺,如果無赞辩,會拋出java.lang.IllegalAccessError異常。(由于JDK9引入了模塊化概念刨沦,Public類型也并非一定可以訪問诗宣,要看本模塊是否有引用類所屬模塊的訪問權(quán)限膘怕。

2.字段解析

用于解析類中的字段想诅,將字段與實(shí)際的所屬類聯(lián)系起來,所以會先解析其父類或?qū)崿F(xiàn)的接口岛心。

  • 字段所屬類本身包含該字段的簡單名稱和字段描述符来破,則返回這個(gè)字段的直接引用。
  • 否則忘古,先搜索其實(shí)現(xiàn)的接口徘禁,按繼承關(guān)系從下往上搜索所有父接口,直到找到匹配的簡單名稱和字段描述符髓堪。
  • 否則送朱,在搜索其父類,按繼承關(guān)系往上干旁,直到找到匹配的簡單名稱和字段描述符驶沼。
  • 否則,查找失敗争群,拋出java.lang.NoSuchFieldError異常回怜。

注意:Oracle公司的javac編譯器在實(shí)現(xiàn)時(shí),更加嚴(yán)格换薄。當(dāng)該類本身及父類玉雾、所實(shí)現(xiàn)接口中,都有該字段的簡單名稱和字段描述符時(shí)轻要,會報(bào)錯(cuò)复旬。Javac編譯器將提示“The field Sub.A is ambiguous”,并且會拒絕編譯這段代碼冲泥。

3.方法解析

  • 解析方法所屬類驹碍,并加載類失都,解析成功時(shí)繼續(xù)后續(xù)
  • 若在類的方法表中進(jìn)行方法解析,但是卻指向了接口類型的常量池索引幸冻,會報(bào)錯(cuò)Java.lang.IncompatibleClassChangeError粹庞,若無錯(cuò),則繼續(xù)
  • 此后便是檢查該方法是在本類洽损、還是父類庞溜、所實(shí)現(xiàn)的接口類中。如果沒有找到拋出java.lang.NoSuchMethodError碑定。
  • 查找并成功返回來直接引用后流码,會對這個(gè)方法進(jìn)行權(quán)限驗(yàn)證,如果不具備訪問該方法權(quán)限延刘,則拋出java.lang.IllegalAccessError

4.接口方法解析

  • 解析接口方法所屬接口的符號引用漫试,并解析
  • 如上,如果在接口方法表中發(fā)現(xiàn)方法引用的常量類型是類碘赖,則報(bào)不匹配類型改變錯(cuò)誤驾荣。正常則繼續(xù)
  • 沿本接口、父接口一直往上普泡,直到找到匹配的方法播掷。如果找到返回其直接引用。當(dāng)多個(gè)接口時(shí)撼班,具體實(shí)現(xiàn)要看具體的編譯器歧匈,有的嚴(yán)格編譯器可能會在多個(gè)接口都有匹配方法時(shí),進(jìn)行拒絕編譯砰嘁。
  • 如果沒找到件炉,拋出沒有該方法異常。

⑤初始化

感覺這老師講得太爛了矮湘,不適合初學(xué)者學(xué)習(xí)斟冕。


類加載器

功能:用于根據(jù)全限定名,加載字節(jié)流到虛擬機(jī)內(nèi)存中板祝。

注意:當(dāng)同一個(gè)類文件由兩個(gè)類加載器加載時(shí)宫静,其在虛擬機(jī)中不屬于同一個(gè)類,因?yàn)槊總€(gè)類加載器有自己類名字列表空間券时。

啟動(dòng)類加載器(Bootstrap ClassLoader):啟動(dòng)類加載器是用C++語言實(shí)現(xiàn)的孤里,是虛擬機(jī)自身的組成部分。

  • 功能:用于加載存放在<JAVA_HOME>\lib目錄下的橘洞、-Xbootclasspath參數(shù)指定路徑下的捌袜、java虛擬機(jī)能夠識別的類庫到虛擬機(jī)內(nèi)存中(按文件名和后綴識別)。
  • 說明:無法被類加載器直接引用炸枣,當(dāng)需要讓Bootstrap ClassLoader來加載類時(shí)虏等,設(shè)置ClassLoader 實(shí)例=null即可弄唧。

擴(kuò)展類加載器(Extension CLassLoader):sun.misc.Launcher$ExtClassLoader中以java代碼實(shí)現(xiàn)。

  • 功能:用于加載存放在<JAVA_HOME>\lib\ext目錄下的霍衫、java.ext.dirs系統(tǒng)變量所指定的路勁下的所有類庫候引。用戶可以將拓展的類庫放在<JAVA_HOME>\lib\ext下來對JAVA SE拓展。
  • 說明:因?yàn)橥卣诡惣虞d器是由java寫成敦跌,所以可以直接在java代碼中使用拓展類加載器來加載CLass文件澄干。

應(yīng)用程序加載器(Application ClassLoader):(因其是ClassLoader.getSystemClassLoader()的返回值,也稱系統(tǒng)類加載器)sun.misc.Launcher$AppClassLoader中以java代碼實(shí)現(xiàn)柠傍。

  • 功能:用于加載用戶編寫的類麸俘,所在路徑的所有類庫。
  • 說明:開發(fā)者可以直接在代碼中使用這個(gè)類惧笛。并且當(dāng)沒有自定義的類加載器時(shí)从媚,其會作為默認(rèn)類加載器。
雙親委派模型(Parents Delegation Model)--三層類加載器
24.jpg

說明:JDK9之前的java應(yīng)用都是由啟動(dòng)患整、拓展拜效、應(yīng)用類加載器互相配合完成加載。當(dāng)想要從磁盤外以及其他路徑加載類或通過加載器實(shí)現(xiàn)類的隔離并级、重載等功能時(shí)拂檩,用戶可以自定義類來進(jìn)行拓展。其非強(qiáng)制性模型嘲碧,但由java官方推薦使用這個(gè)模型。

層次結(jié)構(gòu):雙親委派模型中父阻,父子關(guān)系不是繼承愈涩,而是組合。如自定義類加載器可以組合應(yīng)用程序類加載器加矛。除了頂層的啟動(dòng)類加載器外履婉,所有的類加載器都應(yīng)該要有父輩類加載器。

工作過程(先上后下):當(dāng)一個(gè)類加載器收到類加載請求斟览,其首先會向父輩類加載器傳遞這個(gè)請求毁腿,而父輩類加載器也會向其父輩類加載器傳遞,直到最頂苛茂。當(dāng)父輩在自己的類加載路徑目錄下找不到該類時(shí)已烤,就會讓子輩類加載器來加載凤壁,以此又往下傳遞实胸。

優(yōu)點(diǎn)

  • 保證程序穩(wěn)定運(yùn)作:越基礎(chǔ)的類由越上層的類加載器加載墓懂;這種層次結(jié)構(gòu)及工作方式浪册,讓系統(tǒng)類庫的類迹卢,永遠(yuǎn)都是由固定的類加載器加載虱歪。如java.lang.Object類时迫,處在rt.jar中娇昙,將由啟動(dòng)類加載器加載。就算用戶自定義了一個(gè)同名類剥哑,但永遠(yuǎn)也無法加載硅则,因?yàn)槊看味紩虞dObject類。
  • 實(shí)現(xiàn)模型的代碼十分簡單:僅十余行代碼株婴,先檢查請求加載的類型是否已經(jīng)被加載過抢埋,若沒有則調(diào)用父加載器的loadClass()方法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器督暂。假如父類加載器加載失敗揪垄,拋出ClassNotFoundException異常的話,才調(diào)用自己的findClass()方法嘗試進(jìn)行加載逻翁。

延伸:關(guān)于熱部署等方法饥努,以及JDBC、JNDI等"破壞"了模型八回,但是卻解決了問題酷愧。

java模塊化系統(tǒng)(Java Platform Module System,JPMS)

模塊:JDK9之后引入模塊化系統(tǒng)缠诅,如jar(archive溶浴,文檔)包中, 之前僅用作類庫的容器管引,而現(xiàn)在其還可以包含模塊的信息(實(shí)現(xiàn)封裝隔離機(jī)制)士败。包括:所依賴的模塊列表、導(dǎo)出的包列表(其他模塊可使用)褥伴、開放的包列表(其他模塊可反射訪問的列表)谅将、使用的服務(wù)列表、提供服務(wù)的實(shí)現(xiàn)列表重慢。

類路徑和模塊路徑(ModulePath):jdk9后將路徑分為類路徑和模塊路徑饥臂。類路徑上的全部以傳統(tǒng)jar包看待(就算包含了模塊化信息);模塊路徑上的jar或jmod文件全部以模塊看待似踱;

訪問規(guī)則

  • JAR文件在類路徑訪問規(guī)則:將所有類路徑上的jar文件及其他資源文件隅熙,看做放在了一個(gè)匿名模塊里。其可以看到本模塊內(nèi)所有包核芽、jdk系統(tǒng)模塊中所有的導(dǎo)出包囚戚、模塊路徑上所有模塊的導(dǎo)出包。
  • JAR文件在模塊路徑訪問規(guī)則:盡管不包含module-info.class文件狞洋,其只要在模塊路徑上弯淘,就被當(dāng)做模塊對待。默認(rèn)依賴于整個(gè)模塊路徑上的所有模塊(可訪問他們的導(dǎo)出包)吉懊,且默認(rèn)導(dǎo)出其所有的包庐橙。
  • 模塊在模塊路徑的訪問規(guī)則:普通模塊稱為具名模塊(Named Module)假勿,只能訪問其所列出的依賴模塊和包,對jar文件(匿名模塊)里的所有內(nèi)容不可見态鳖。

模塊化下的類加載器

變更:

  • 拓展類加載器 被 平臺類加載器 取代转培。

    因?yàn)槟K化本身具足拓展性,不需要再有<JAVA_HOME>\lib\ext目錄和系統(tǒng)變量java.ext.dirs和拓展類加載器了

  • <JAVA_HOME>\jre也被取消浆竭。

    而jre也可以隨時(shí)通過模塊浸须,構(gòu)建出一個(gè)運(yùn)行環(huán)境。如:

    jlink -p $JAVA_HOME/jmods --add-modules java.base --output jre
    
  • 平臺類加載器 和 應(yīng)用程序加載器 取消繼承自java.net.URLCLASSLoader 邦泄,轉(zhuǎn)而與啟動(dòng)類加載器一起繼承自jdk.internal.loader.BuiltinClassLoader删窒,該類實(shí)現(xiàn)了模塊化下的類加載邏輯,及資源可訪問性處理顺囊。

    如果有程序依賴這個(gè)繼承關(guān)系肌索,或者依賴于URLClassLoader的特定方法,那代碼可能會在JDK9及以后版本中崩潰特碳。

  • 如上述诚亚,啟動(dòng)類加載器也變成虛擬機(jī)內(nèi)部和java類庫共同協(xié)作實(shí)現(xiàn)的類加載器了。但是調(diào)用方式還是要自定義類加載器并賦值為null.

  • 雙親委派模型中午乓,類加載器關(guān)系也發(fā)生變化站宗。

25.jpg

系統(tǒng)模塊

歸屬:系統(tǒng)模塊有規(guī)定的類加載器,當(dāng)加載一個(gè)模塊時(shí)益愈,在向父輩傳遞請求之前梢灭,先判斷是否是系統(tǒng)模塊,及其歸屬的類加載器是哪個(gè)腕唧,并將請求交給他或辖。若非系統(tǒng)模塊,才交給父類傳遞枣接。

BootStrap ClassLoader負(fù)責(zé)加載的模塊:

java.base java.datatransfer java.desktop java.instrement
java.logging java.management java.management.rmi java.naming
java.prefs java.rmi(遠(yuǎn)程方法調(diào)用) java.security.sasl java.xml
jdk.httpserver jdk.internal.vm.ci jdk.management jdk.management.agent
jdk.naming.rmi jdk.net jdk.sctp jdk.unsupported

Platform ClassLoader負(fù)責(zé)加載的模塊

26.jpg

crypto(加密)、incubator(孵化器)

Application ClassLoader負(fù)責(zé)加載的模塊

27.jpg


虛擬機(jī)字節(jié)碼執(zhí)行引擎

概述:與物理機(jī)不同缺谴,虛擬機(jī)用軟件層面的執(zhí)行引擎但惶,來對二進(jìn)制字節(jié)碼流進(jìn)行處理。通常執(zhí)行引擎運(yùn)作方式為:解釋執(zhí)行和編譯執(zhí)行湿蛔。不同虛擬機(jī)實(shí)現(xiàn)中膀曾,選擇的方法可能不同,單一或者搭配阳啥,或者按等級結(jié)構(gòu)分配執(zhí)行引擎添谊。

運(yùn)行時(shí)棧幀結(jié)構(gòu)

棧幀:儲存了函數(shù)方法的局部變量、操作數(shù)棧察迟、動(dòng)態(tài)連接斩狱、方法返回地址等耳高。不同棧幀作為不同方法的所有物,是完全獨(dú)立的所踊。

棧幀生命周期:從調(diào)用一個(gè)方法開始泌枪,到執(zhí)行結(jié)束的過程。也是其在虛擬機(jī)棧里秕岛,入棧到出棧的過程碌燕。

當(dāng)前幀棧:對執(zhí)行引擎來說,只有最頂?shù)膸瑮J钦趫?zhí)行的幀棧继薛,引擎所執(zhí)行的所有字節(jié)碼都只針對該幀棧進(jìn)行操作修壕。

細(xì)節(jié):

  • 棧幀中的局部變量表所需空間、棧深度遏考,都已經(jīng)在編譯的時(shí)候確定并寫入Code表中慈鸠。運(yùn)行時(shí)不會改變。

棧幀結(jié)構(gòu)示意圖:

28.jpg

局部變量表:

組成:多個(gè)槽構(gòu)成诈皿。用于存儲 方法參數(shù) 和 方法內(nèi)部定義的局部變量林束。

  • :《java虛擬機(jī)規(guī)范》并沒有規(guī)定其大小,而是只要能大于等于32位就行(放下32位以內(nèi)的所有類型)稽亏。64位分成兩個(gè)壶冒,高位在前。

  • 虛擬機(jī)數(shù)據(jù)類型:reference=32位截歉。注意胖腾!其和java語言數(shù)據(jù)類型不同。尤其是其中的引用reference是占一個(gè)槽瘪松,還有一個(gè)returnAddress類型也是一個(gè)槽咸作。(returnAddress為執(zhí)行一條字節(jié)碼指令的地址,不常見了宵睦,其作為古老jdk上面異常處理跳轉(zhuǎn)的助手)

儲存結(jié)構(gòu):類似于數(shù)組记罚,當(dāng)全是32位以下類型時(shí),第n個(gè)數(shù)據(jù)壳嚎,就放在第n個(gè)槽桐智。64位數(shù)據(jù),則占據(jù)2槽烟馅,放在n说庭、n+1位(高位在前)。

64位的數(shù)據(jù)郑趁,虛擬機(jī)不允許任何方式單獨(dú)訪問其中一個(gè)槽刊驴。校驗(yàn)階段會發(fā)生異常。

方法調(diào)用過程:(對于實(shí)參到形參的傳遞,使用棧幀的局部變量表來完成捆憎;運(yùn)行調(diào)用時(shí)舅柜,生成如下過程)

  • 1.如果執(zhí)行的是對象實(shí)例的方法(不是直接Class.(static)function),則局部變量表中攻礼,第0個(gè)槽业踢,即引用==0位置的槽,會放置上其所屬對象的引用礁扮。
  • 2.從第一個(gè)槽開始知举,依實(shí)參順序依次放入變量表中。
  • 3.然后依方法內(nèi)的局部變量先后順序及作用域太伊,將他們依次放入變量表中雇锡。

局部變量表槽復(fù)用:在一個(gè)函數(shù)方法內(nèi),當(dāng)有多個(gè)作用域時(shí)僚焦,槽的數(shù)量不一定就是所有變量數(shù)量和锰提,而是可以進(jìn)行復(fù)用。如有多個(gè)"{...}"區(qū)域存在芳悲,其中的代碼的作用域到"}"為止立肘。(疑問:意思是不一次放入所有變量到局部變量表么?)


操作數(shù)棧:

組成:一個(gè)后進(jìn)先出的棧數(shù)據(jù)結(jié)構(gòu)名扛。最大棧深度谅年,編譯時(shí)已被寫入Code屬性的max_stacks數(shù)據(jù)項(xiàng)中。

  • 棧元素可包括64位肮韧,其占兩個(gè)棧容量融蹂。普通32位占一個(gè)棧容量。

功能:用于完成字節(jié)碼中的操作弄企。如:iadd指令超燃,執(zhí)行時(shí)會將操作數(shù)棧頂兩個(gè)元素進(jìn)行出棧,并相加拘领,隨后壓入棧中意乓。其前面要有類似兩個(gè)iload指令,將int整數(shù)放入棧中约素。

  • 注意:虛擬機(jī)字節(jié)碼指令執(zhí)行洽瞬,對數(shù)據(jù)要求是絕對匹配的。如iadd业汰,操作棧中的棧頂兩個(gè)元素必須是int型,不能是float菩颖、double样漆、long等。否則編譯階段就會報(bào)錯(cuò)晦闰,就算通過或手寫字節(jié)碼放祟,虛擬機(jī)驗(yàn)證階段也會報(bào)錯(cuò)鳍怨。

操作數(shù)棧共享

說明:出于節(jié)約空間和共享數(shù)據(jù)理念,有部分操作數(shù)棧區(qū)域可以共享跪妥。

29.jpg

動(dòng)態(tài)連接

組成:一個(gè)引用鞋喇。其指向運(yùn)行時(shí)方法區(qū):常量池里,該棧幀所屬的方法眉撵。


方法返回地址:

組成:保存返回地址侦香。


說明:動(dòng)態(tài)鏈接、方法返回地址纽疟、其他附加信息等組合起來稱為棧幀信息罐韩。


方法調(diào)用

解析:當(dāng)虛擬機(jī)進(jìn)行解析操作時(shí),只有類里的static污朽、private方法會被解析成直接引用散吵,因?yàn)槠洳粫谶\(yùn)行時(shí)更改了。

方法調(diào)用指令集:

  • invokestatic:調(diào)用靜態(tài)方法
  • invokespecial:調(diào)用實(shí)例構(gòu)造器< init>()方法蟆肆、私有方法和父類中的方法矾睦。
  • invokevirtual:用于調(diào)用所有的虛方法。
  • invokeinterface:調(diào)用接口方法
  • invokedynamic:在運(yùn)行時(shí)炎功,動(dòng)態(tài)解析出調(diào)用點(diǎn)的限定符所引用的方法枚冗,然后執(zhí)行該方法。

invokestatic和invokespecial都可以在解析時(shí)亡问,確定唯一的調(diào)用版本(方法的內(nèi)部結(jié)構(gòu))官紫,兩個(gè)指令集對應(yīng)的方法,其符號引用在解析時(shí)就生成了直接引用州藕。

非虛方法:靜態(tài)方法束世、實(shí)例構(gòu)造器<init>()方法、私有方法床玻、父類方法毁涉、final修飾的方法。

虛方法:所有其他方法锈死。

分派

靜態(tài)類型:是編譯期間確定的贫堰。注意:即使Human human=new Man(),但是human的類型還是Human待牵,除非強(qiáng)制轉(zhuǎn)換其屏,只有到運(yùn)行時(shí),才會確定其變成Man類型缨该。

實(shí)際類型:(actual type)編譯期間是無法確定其類型的偎行,只有運(yùn)行時(shí)才可以。

// 實(shí)際類型變化
Human human = (new Random()).nextBoolean() ? new Man() : new Woman(); 
// 靜態(tài)類型變化
sr.sayHello((Man) human) 
sr.sayHello((Woman) human)

靜態(tài)分派:依靜態(tài)類型,來決定方法執(zhí)行版本的分派動(dòng)作蛤袒。發(fā)生在編譯階段熄云。最典型應(yīng)用是方法重載。

  • 自動(dòng)轉(zhuǎn)型順序:char>int>long>float>double妙真。(調(diào)用方法缴允,當(dāng)輸入的參數(shù)不對應(yīng)其已有重載方法時(shí)。如:參數(shù)為'a'珍德,若無fun(char x)類型练般,則轉(zhuǎn)化為unicode整數(shù),或繼續(xù)轉(zhuǎn)化成長整數(shù)...)

動(dòng)態(tài)分派:(*難菱阵!書里8.3.2)

單分派和多分派:目前來說踢俄,靜態(tài)多分派,動(dòng)態(tài)單分派晴及。


動(dòng)態(tài)類型語言支持

靜態(tài)和動(dòng)態(tài)類型語言判定:類型檢查是在編譯期間還是運(yùn)行期間都办?

  • 類型檢查:如:obj.fun(),靜態(tài)類型語言在編譯期間虑稼,會確定obj的靜態(tài)類型琳钉,而且其運(yùn)行時(shí)實(shí)際類型必須為其本身或派生。而動(dòng)態(tài)類型語言不會確定obj的本身類型蛛倦,只會在運(yùn)行時(shí)確定其的實(shí)際類型歌懒。
  • 連接時(shí)、運(yùn)行時(shí)異常:連接時(shí)是在類加載階段就報(bào)錯(cuò)溯壶。運(yùn)行時(shí)異常是只有運(yùn)行到異常位置才會報(bào)錯(cuò)及皂,能通過類加載。

注意:動(dòng)態(tài)類型語言與動(dòng)態(tài)語言且改、弱類型語言并不是一個(gè)概念验烧,需要區(qū)別對待。

根本學(xué)不懂又跛,后面在學(xué)吧碍拆。


基于棧的字節(jié)碼解釋執(zhí)行引擎


tomcatd的目錄組織及自定義類加載器

30.jpg

說明:(Tomcat 6之前的結(jié)構(gòu))定義了多個(gè)目錄,提供不同權(quán)限慨蓝,并實(shí)現(xiàn)類庫隔離感混。(Common、Shared礼烈、WebApp\WEB_INF弧满、Server(Catalina類加載器))

每一個(gè)WebApp類加載器和JSP類加載器通常會存在多個(gè)實(shí)例。

Common類加載器能加載的類都可以被Catalina類加載器和Shared類加載器使用此熬,而Catalina類加載器和Shared類加載器自己能加載的類則與對方相互隔離谱秽。

JasperLoader的加載范圍僅僅是這個(gè)JSP文件所編譯出來的那一個(gè)Class文件洽蛀,它存在的目的就是為了被丟棄:當(dāng)服務(wù)器檢測到JSP文件被修改時(shí),會替換掉目前的JasperLoader的實(shí)例疟赊,并通過再建立一個(gè)新的JSP類加載器來實(shí)現(xiàn)JSP文件的HotSwap功能

注意:在Tomcat 6及之后的版本簡化了默認(rèn)的目錄結(jié)構(gòu)(/common、/shared峡碉、/server合并在/bin目錄)近哟,只有指定了tomcat/conf/catalina.properties配置文件的server.loader和share.loader項(xiàng)后才會真正建立Catalina類加載器和Shared類加載器的實(shí)例,否則會用到這兩個(gè)類加載器的地方都會用Common類加載器的實(shí)例代替

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鲫寄,一起剝皮案震驚了整個(gè)濱河市吉执,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌地来,老刑警劉巖戳玫,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異未斑,居然都是意外死亡咕宿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門蜡秽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來府阀,“玉大人,你說我怎么就攤上這事芽突∈哉悖” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵寞蚌,是天一觀的道長田巴。 經(jīng)常有香客問我,道長挟秤,這世上最難降的妖魔是什么壹哺? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮煞聪,結(jié)果婚禮上斗躏,老公的妹妹穿的比我還像新娘。我一直安慰自己昔脯,他們只是感情好啄糙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著云稚,像睡著了一般隧饼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上静陈,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天燕雁,我揣著相機(jī)與錄音诞丽,去河邊找鬼。 笑死拐格,一個(gè)胖子當(dāng)著我的面吹牛僧免,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捏浊,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼懂衩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了金踪?” 一聲冷哼從身側(cè)響起浊洞,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胡岔,沒想到半個(gè)月后法希,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡靶瘸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年苫亦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奕锌。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡著觉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惊暴,到底是詐尸還是另有隱情饼丘,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布辽话,位于F島的核電站肄鸽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏油啤。R本人自食惡果不足惜典徘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望益咬。 院中可真熱鬧逮诲,春花似錦、人聲如沸幽告。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冗锁。三九已至齐唆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冻河,已是汗流浹背箍邮。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工茉帅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锭弊。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓堪澎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親廷蓉。 傳聞我的和親對象是個(gè)殘疾皇子全封,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355