第一部分:自動(dòng)內(nèi)存管理
總圖:
部分名詞解釋:
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挖诸。牽掛你的人
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)容
⑤(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;給定的初始值會作為常量池中的常量烂叔,而附加屬性對其引用谨胞。
⑨(u2)方法區(qū)數(shù)量。
⑩(4對u2+屬性表集合)方法區(qū):屬性表集合擁有很多屬性_info蒜鸡,具體看下文
常量池內(nèi)容
方法表
和字段表很類似胯努。其屬性表中會有code
結(jié)構(gòu)為:4個(gè)u2+方法的屬性表:(順序)access_flag、name_index逢防、descriptor_index(說明參數(shù)和返回值)叶沛、屬性表個(gè)數(shù)、n個(gè)具體屬性表(如Code表忘朝,下面有詳述)
屬性表
屬性表通用格式:
屬性表集合:
Code屬性表:
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è)異常屬性表示弓,列出的是方法拋出的異常讳侨。
格式:
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)耕渴。
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)閉。
格式:
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)問題的行號虹茶。
格式:
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
Signature屬性表
BootStrapMethods屬性表
MethodParameters屬性表
還有兩個(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ī)制
類的生命周期:
強(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)--三層類加載器
說明: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ā)生變化站宗。
系統(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é)加載的模塊:
crypto(加密)、incubator(孵化器)
Application ClassLoader負(fù)責(zé)加載的模塊
虛擬機(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)示意圖:
局部變量表:
組成:多個(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ū)域可以共享跪妥。
動(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的目錄組織及自定義類加載器
說明:(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í)例代替