JVM虛擬機(jī)學(xué)習(xí)
Jdk:java developmentkit
Jre:java runtime environment
1:自動(dòng)內(nèi)存管理機(jī)制
Java內(nèi)存區(qū)域與內(nèi)存溢出異常
Java的內(nèi)存區(qū)域結(jié)構(gòu)如下

程序計(jì)數(shù)器:
1:為了應(yīng)對(duì)多線程運(yùn)行環(huán)境,每個(gè)線程都會(huì)有獨(dú)立一個(gè)程序計(jì)數(shù)器,記錄自己執(zhí)行到哪個(gè)指令,某線程執(zhí)行完了一條語(yǔ)句,會(huì)改變自己的程序計(jì)數(shù)器的值來(lái)指向下一條指令。
每個(gè)線程的程序計(jì)數(shù)器之間互不影響,獨(dú)立存儲(chǔ),為線程私有的內(nèi)存
2:如果線程在執(zhí)行一個(gè)java方法,則程序計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果執(zhí)行的是native方法,這個(gè)計(jì)數(shù)器值為空。
?????? 3:此內(nèi)存區(qū)域是虛擬機(jī)中唯一一個(gè)沒有OutOfMemoryError情況的區(qū)域
Java虛擬機(jī)棧:
1:Java虛擬機(jī)棧是線程私有的,他的生命周期和線程相同。即啟動(dòng)一個(gè)線程時(shí),會(huì)在該線程內(nèi)部啟動(dòng)一個(gè)虛擬機(jī)棧,用來(lái)管理這個(gè)線程執(zhí)行過的所有方法。
2:每一個(gè)方法在調(diào)用時(shí)會(huì)創(chuàng)建一個(gè)棧幀,將這個(gè)棧幀放入虛擬機(jī)棧中,等待方法執(zhí)行完畢后,將這個(gè)棧幀進(jìn)行出棧。
3:一個(gè)棧幀中存放的有:局部變量表,動(dòng)態(tài)鏈接(???),操作數(shù)棧(???),方法出口等信息
4:局部變量表存放了編譯期可知的所有基本數(shù)據(jù)類型(boolean,byte,char,short,int,long,float,double),對(duì)象引用(reference類型,但不等同于對(duì)象本身,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔槪┖蛂eturnAddress類型(執(zhí)行了一條字節(jié)碼指令的地址)
5:如果線程請(qǐng)求的棧深度大于虛擬機(jī)棧的深度,將拋出stackoverflow異常
6:如果虛擬機(jī)站可以動(dòng)態(tài)擴(kuò)展,且擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryErrory異常
本地方法棧:
1:本地方法棧和java虛擬機(jī)棧是差不多的,只不過本地方法棧的作用對(duì)象是native方法,java虛擬機(jī)棧的作用對(duì)象是java方法。
2:和虛擬機(jī)棧相同的是,本地方法棧區(qū)域也會(huì)拋出StackoverFlow和OutOfMemoryError異常。
Java堆—垃圾收集器管理的主要區(qū)域
1:在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
2:被所有線程共享的一塊內(nèi)存區(qū)域
3: 此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存。
4:從內(nèi)存回收的角度來(lái)說(shuō),java堆可細(xì)分為新生代和老年代,再細(xì)致一點(diǎn)可分為,Eden空間,F(xiàn)rom Survivor空間,To Survivor空間
5:從內(nèi)存分配角度來(lái)看,線程共享的java堆可能劃分出多個(gè)線程私有的分配緩沖區(qū)。
6:java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的就行
方法區(qū)
1:是每個(gè)線程共享的內(nèi)存區(qū)域。
2:存儲(chǔ)應(yīng)用啟動(dòng)時(shí)加載的相關(guān)類信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼。
?????? 3:這個(gè)區(qū)域的垃圾回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載(也是垃圾回收左右的范圍之一)
直接內(nèi)存:
?????? 1:是在虛擬機(jī)對(duì)內(nèi)存之外,但屬于機(jī)器內(nèi)存的一塊經(jīng)常使用的內(nèi)存
?????? 2:使用native函數(shù)庫(kù)直接分配堆外內(nèi)存,再通過java堆中的DirectByteBuffer來(lái)操作這塊內(nèi)存
運(yùn)行時(shí)常量池
1:運(yùn)行時(shí)常量池是方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號(hào)引用。
Java堆
對(duì)象的創(chuàng)建:
虛擬機(jī)遇到一個(gè)new指令時(shí),先會(huì)去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,檢查這個(gè)類是否已被加載,解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。
類加載檢查通過后,這時(shí)對(duì)象所需要分配的空間就可以完全確定下來(lái),虛擬機(jī)按照這個(gè)大小為新生對(duì)象分配內(nèi)存空間(堆內(nèi)存空間)
接下來(lái),虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,比如如何確定這是那個(gè)對(duì)象的實(shí)例,如何才能找到元數(shù)據(jù)信息,對(duì)象的hash碼,對(duì)象的GC分代年齡等信息,這些信息存放在信息頭中。
上面過程做完了之后,其實(shí)只是將這個(gè)對(duì)象與類進(jìn)行關(guān)聯(lián),分配內(nèi)存等。還沒對(duì)這個(gè)對(duì)象進(jìn)行初始化,
對(duì)象的內(nèi)存布局:
對(duì)象在內(nèi)存中的存儲(chǔ)布局分為三個(gè)部分:對(duì)象頭(Header),實(shí)例數(shù)據(jù)(Instance Data),對(duì)齊填充(Padding)
對(duì)象頭:
????????????? 1:用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),稱之為MarkWord(用于在對(duì)象鎖處理時(shí)重點(diǎn)關(guān)注的地方),記錄諸如哈希碼,GC分代年齡,鎖狀態(tài)標(biāo)志,線程持有的鎖,偏向線程的ID等等
????????????? 2:用于存儲(chǔ)類型指針,即對(duì)象指向它的類元數(shù)據(jù)的指針,指明它屬于哪一個(gè)類的實(shí)例對(duì)象
????????????? 3:如果對(duì)象是一個(gè)數(shù)組,那么對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù),因?yàn)樘摂M機(jī)需要從普通java對(duì)象的元數(shù)據(jù)信息確定數(shù)組大小,但是從數(shù)組元數(shù)據(jù)中無(wú)法獲取數(shù)組大小信息
?實(shí)例數(shù)據(jù):
????????????? 1:存儲(chǔ)對(duì)象的信息,比如某個(gè)字段屬性的實(shí)際值,無(wú)論是父類繼承下來(lái)的還是子類中自己定義的,都需要記錄
?對(duì)齊填充:
????????????? 1:沒有特殊意義,僅起到占位符的作用。原因是因?yàn)镠otSpot要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,如果實(shí)例數(shù)據(jù)部分的數(shù)據(jù)不夠8字節(jié)的整數(shù)倍,那么就需要對(duì)齊填充來(lái)完成
對(duì)象的訪問定位:
?????? 通過java虛擬機(jī)棧中存儲(chǔ)的reference類型來(lái)確定調(diào)用對(duì)象的地址
????????????? 1:如果使用句柄訪問的話,那么java堆中會(huì)另外開辟出一塊內(nèi)存來(lái)作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中存儲(chǔ)對(duì)象實(shí)例數(shù)據(jù)和對(duì)象所屬類元數(shù)據(jù)各自的地址信息,如圖:

?????? 2:如果使用直接指針訪問,reference中存儲(chǔ)的是直接對(duì)象地址,包括實(shí)例數(shù)據(jù)部分和實(shí)例所屬類元數(shù)據(jù)的地址信息,如圖

垃圾收集器與內(nèi)存分配策略
如何判定對(duì)象是否存活?
引用計(jì)數(shù)算法:
?????? 1:原理:給對(duì)象添加一個(gè)引用計(jì)數(shù)器,但有一個(gè)對(duì)象引用它時(shí),就加一,當(dāng)引用失效時(shí),就減一,任何時(shí)刻計(jì)數(shù)器的值為0時(shí),該對(duì)象就不會(huì)再被引用
?????? 2:優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,判定效率高
?????? 3:缺點(diǎn):很難解決對(duì)象之間循環(huán)引用的問題
可達(dá)性分析算法:
從一系列GC ROOT為初始節(jié)點(diǎn)開始向下搜索,節(jié)點(diǎn)之間的直線成為引用鏈,當(dāng)一個(gè)對(duì)象到這些GC ROOT之間沒有任何引用鏈時(shí),則證明此對(duì)象是不可用的。如圖:

可作為GC
ROOT的對(duì)象包括以下幾種:
1:虛擬機(jī)棧中局部變量表中引用的對(duì)象(reference類型)
2:方法區(qū)中靜態(tài)屬性引用的對(duì)象
3:方法區(qū)中常量引用的對(duì)象
4:本地方法棧中Native方法引用的對(duì)象
四種引用類型:
強(qiáng)引用:類似 Object obj = new Object();只要強(qiáng)引用還在,就不會(huì)被回收
軟引用:描述一些還有用但非必需的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出之前,將會(huì)把對(duì)象列入回收范圍之內(nèi)進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存,則會(huì)拋出內(nèi)存溢出異常。
提供SoftReference類來(lái)實(shí)現(xiàn)軟引用
弱引用:描述非必需的對(duì)象,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)開始進(jìn)行垃圾回收時(shí),無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收弱引用的對(duì)象。提供WeakReference類來(lái)實(shí)現(xiàn)弱引用
虛引用:最弱的一種引用,一個(gè)對(duì)象是否有虛引用存在,完全不會(huì)影響其生存時(shí)間,也無(wú)法通過虛引用來(lái)取得一個(gè)對(duì)象引用,提供PhantomReference引用
回收方法區(qū):(HotSpot虛擬機(jī)中的永久代)
主要包括回收廢棄常量和無(wú)用的類
判定廢棄常量的標(biāo)準(zhǔn)是某一個(gè)常量進(jìn)入了常量池,但是沒有任何一個(gè)對(duì)象是這個(gè)常量的引用,則可以將這個(gè)常量廢棄
判定無(wú)用的類的標(biāo)準(zhǔn):
?????? 1:該類的所有實(shí)例對(duì)象都已被回收
?????? 2:該類的ClassLoader類已經(jīng)被回收,如果不自定義ClassLoader類,那么系統(tǒng)中所有的類都是“有用”類
?????? 3:沒有任何地方有調(diào)用這個(gè)類的地方,甚至不能通過反射機(jī)制訪問該類的方法
垃圾收集算法:
標(biāo)記--清除算法
前提(沒有跟GC ROOT相關(guān)聯(lián)的對(duì)象并不會(huì)被立即回收,會(huì)經(jīng)歷兩次回收動(dòng)作,第一次會(huì)被打上標(biāo)記(對(duì)象沒有覆蓋finalize()方法,或者finalize方法已經(jīng)被虛擬機(jī)調(diào)用過),第二次會(huì)被回收)
首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。
缺點(diǎn):一是效率問題,標(biāo)記和清除兩個(gè)過程的效率都不高
另一個(gè)是空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,內(nèi)存碎片太多可能會(huì)導(dǎo)致以后需要分配較大對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾回收動(dòng)作

復(fù)制算法:
將內(nèi)存按容量劃分為大小相同的兩塊,每次只是使用其中的一塊,當(dāng)這塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另一塊上去,然后再把已使用過的內(nèi)存空間一次清理掉,然后吧剩余的不回收的內(nèi)存塊,移動(dòng)堆頂指針按順序分配內(nèi)存。
缺點(diǎn)在于每次就只用一半的內(nèi)存。
應(yīng)用場(chǎng)景:
新生代中98%的對(duì)象都會(huì)很快的回收掉,所以并不需要按照1:1的比例來(lái)劃分內(nèi)存空間,新生代分為eden區(qū),survivor一區(qū),survivor二區(qū),比例是8:1:1,當(dāng)回收時(shí),需要將eden區(qū)和一區(qū)的對(duì)象進(jìn)行回收,然后放到二區(qū)中。如果,萬(wàn)一說(shuō),回收后存活的量大于10%,那么多余的將會(huì)放到老年區(qū)。

?
標(biāo)記--整理算法
復(fù)制算法存在大量的復(fù)制操作,而且,如果存在極端情況比如所有的對(duì)象都不需要回收,或者90%以上的對(duì)象都不需要回收,那么就需要有額外的空間來(lái)提供擔(dān)保空間針對(duì)老年代的回收。
標(biāo)記整理算法是先將需要回收的對(duì)象進(jìn)行標(biāo)記,把不需要回收的對(duì)象向一端移動(dòng),然后清理掉邊界以外的內(nèi)存。

分代收集算法:
對(duì)新生代的區(qū)域,使用復(fù)制算法只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集。
對(duì)于老年代的收集,因?yàn)榇婊盥矢?,沒有額外的擔(dān)??臻g,就必須使用標(biāo)記-清理或者標(biāo)記-整理算法
3.4? HotSpot的算法實(shí)現(xiàn)
枚舉根節(jié)點(diǎn):
在進(jìn)行枚舉根節(jié)點(diǎn)時(shí),理論上我們需要將全系統(tǒng)的上下文掃描一遍,這種方式對(duì)大系統(tǒng)來(lái)說(shuō)會(huì)很耗時(shí),無(wú)法在GC時(shí)進(jìn)行可達(dá)性分析進(jìn)行。因此虛擬機(jī)需要使用某種方式獲取哪些地方使用了哪些對(duì)象的引用。再HotSpot中,使用OopMap數(shù)據(jù)接口來(lái)存儲(chǔ)這些對(duì)象引用。在類加載完畢后,HotSpot就將對(duì)象內(nèi)使用的其他對(duì)象引用計(jì)算出來(lái),放入OopMap中, 這樣GC在掃描時(shí)就可以直接得知這些信息。
安全點(diǎn):
當(dāng)執(zhí)行系統(tǒng)停頓下來(lái)以后,并不需要一個(gè)不漏的檢查完所有的執(zhí)行上下文和全局的引用位置,虛擬機(jī)可以通過OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)達(dá)到這個(gè)目的,用來(lái)存儲(chǔ)哪些地方存放著對(duì)象引用,在JIT編譯過程中,也會(huì)在“特定的位置”記錄下棧和寄存器中哪些位置時(shí)引用。但如果OopMap存儲(chǔ)的指令非常多,如果為每個(gè)指令都生成對(duì)應(yīng)的OopMap,會(huì)造成空間成本特別高。因此設(shè)置了安全點(diǎn),
如果同時(shí)使用final和static來(lái)修飾一個(gè)變量(常量),并且這個(gè)變量的數(shù)據(jù)類型是基本類型或者讓java.lang.string的話,就生成ConstantValue屬性來(lái)進(jìn)行初始化;如果這個(gè)變量沒有被final修飾,或者并非基本類型及字符串,則將會(huì)選擇在<cl-init>方法中進(jìn)行初始化。
注:ConstantValue? =? final+ static? +(基本類型/String)
cl-init?=? 沒有被final修飾或者并非基本類型及字符串
內(nèi)存分配與回收策略
對(duì)象優(yōu)先再Eden區(qū)分配對(duì)象,如果Eden區(qū)內(nèi)存不夠用,虛擬機(jī)就會(huì)發(fā)起一次針對(duì)Eden區(qū)的垃圾回收,使用復(fù)制算法,將Eden和Survivor-1的存活對(duì)象分配到Survivor-2中(下一次將把Eden和Survivor-2的存貨對(duì)象分配到Survivor-1中),如果有多余的,將會(huì)通過擔(dān)保方式進(jìn)入老年代。如果是大對(duì)象(典型的時(shí)數(shù)組大對(duì)象),則直接進(jìn)入老年代。可以通過-XX:PretenureSizeThreshold來(lái)設(shè)定大于這個(gè)值的對(duì)象就進(jìn)入老年代。對(duì)象在Survivor區(qū)中每經(jīng)歷一次垃圾回收,就將年齡增加1,當(dāng)達(dá)到15時(shí)(默認(rèn))還沒有被回收的話,就將進(jìn)入老年帶,可以通過-XX:MaxTenuringThreshold來(lái)設(shè)定年齡大小。如果Survivor中某個(gè)年齡的對(duì)象數(shù)量占用Survivor一半以上的內(nèi)存,則年齡大于等于這個(gè)年齡的直接進(jìn)入老年代
空間分配擔(dān)保:
Eden,Survivor區(qū)垃圾回收后,如果另一個(gè)Survivor不夠裝下剩余存活的對(duì)象,則有對(duì)象要進(jìn)入老年代,這時(shí)候虛擬機(jī)會(huì)有 先檢查自己剩余的連續(xù)空間是否能裝下這些對(duì)象,如果足夠,則順利進(jìn)行。如果不夠,看HandlePromotionFailure設(shè)置是否允許擔(dān)保失敗,如果不允許,則需要進(jìn)行一次Full GC(Full GC會(huì)帶來(lái)性能的損耗)。如果允許,則會(huì)查看歷次進(jìn)入老年代的平均值與剩余最大連續(xù)空間,如果是,則嘗試進(jìn)行Minor GC,如果不是,則進(jìn)行一次Full GC
虛擬機(jī)性能監(jiān)控與故障處理工具

Jps—虛擬機(jī)進(jìn)程狀況工具
1:Jps –mlv 可用于顯示正在運(yùn)行的虛擬機(jī)進(jìn)程
?????? 2:使用頻率最高的jdk命令行工具,因?yàn)槠渌膉dk工具大多需要輸入它查詢到的LVMID來(lái)確定要監(jiān)控的是哪一個(gè)虛擬機(jī)今稱
?????? 3:LVMID:本地虛擬機(jī)唯一ID? local virtual machine identifier

Jstat—虛擬機(jī)統(tǒng)計(jì)信息監(jiān)視工具
?????? 1:jstat [ option vmid [interval[ms|s] [count]] ]
?????? 2:用于監(jiān)視虛擬機(jī)各種運(yùn)行狀態(tài)信息的命令行工具,interval代表查詢間隔,默認(rèn)是毫秒為單位,count為查詢次數(shù)

Jinfo-Java配置信息工具
?????? 1:jinfo可以實(shí)時(shí)查看并調(diào)整虛擬機(jī)各項(xiàng)參數(shù)
?????? 2:命令格式:jinfo [option] pid
?????? 3:使用-v參數(shù)可以查看虛擬啟動(dòng)時(shí)顯示指定的參數(shù)列表
?????? 4:使用-flags查看未被顯式指定的參數(shù)列表
?????? 5:使用-sysprops吧虛擬機(jī)進(jìn)程的System.getProperties()的內(nèi)容打印出來(lái)
?????? 6:使用-flag [+|-] name或者-flag name=value修改一部分運(yùn)行期可寫的虛擬機(jī)參數(shù)值

Jmap:java內(nèi)存映像工具
?????? 1:用于生成堆轉(zhuǎn)儲(chǔ)快照(稱為heapdump或者dump文件)
?????? 2:還有另外三種可以獲取java堆轉(zhuǎn)儲(chǔ)快照
????????????? 1:通過-XX:+HeapDumpOnMemoryError參數(shù),可以讓虛擬機(jī)在OOM一場(chǎng)出現(xiàn)之后自動(dòng)生成dump文件
????????????? 2:通過-XX:+HeapDumpOnCtrlBreak參數(shù),可以使用【Ctrl】+【Break】鍵讓虛擬機(jī)生成dump文件
????????????? 3:在linux系統(tǒng)中通過執(zhí)行kill -3命令發(fā)送進(jìn)程推出信號(hào)嚇唬一下虛擬機(jī),也能拿到dump文件
?????? 3:查詢finalize執(zhí)行隊(duì)列
?????? 4:查詢java堆的詳細(xì)信息
?????? 5:查詢永久代的詳細(xì)信息
?????? 6:命令格式:jmap [ option ] vmid

Jstack—java線程堆棧跟蹤工具
?????? 1:用于生成虛擬機(jī)當(dāng)前時(shí)刻的線程快照(threaddump或者javacore文件)。就是當(dāng)前虛擬機(jī)每一條線程正在執(zhí)行的方法堆棧的集合。目的:為了分析定位線程出現(xiàn)長(zhǎng)時(shí)間停頓的原因,如死鎖,死循環(huán),請(qǐng)求外部資源導(dǎo)致的長(zhǎng)時(shí)間等待??梢灾谰€程正在做什么事情
?????? 2:命令格式:jstack [ option ] vmid

JConsole--Java監(jiān)視與管理控制臺(tái)
展示功能包括,概述,內(nèi)存,線程,類,VM摘要,MBean
VisualVM—多合一故障處理工具
?????? 1:顯示虛擬機(jī)進(jìn)程以及進(jìn)程的配置,環(huán)境信息(jps,jinfo)
?????? 2:監(jiān)視應(yīng)用程序的CPU,GC,堆,方法區(qū)以及線程的信息(jstat,jstack)
?????? 3:dump以及分析堆轉(zhuǎn)儲(chǔ)快照(jmap,jhat)
?????? 4:方法級(jí)的程序運(yùn)行性能分析,能夠找出被調(diào)用最多,運(yùn)行時(shí)間最長(zhǎng)的方法
?????? 5:提供BTrace動(dòng)態(tài)日志跟蹤
????????????? 1>:作用:通過通過HotSwap技術(shù)動(dòng)態(tài)加入原本并不存在調(diào)試代碼
????????????? 2>:應(yīng)用場(chǎng)景:比如在需要打印日志進(jìn)行排錯(cuò)的時(shí)候,加入原本不存在的日志輸出代碼
比如:需要在BTraceTest方法add中打印出a,b的值

通過創(chuàng)建如下代碼,可實(shí)現(xiàn)在add方法中打印參數(shù)a,b

虛擬機(jī)類加載機(jī)制
類文件結(jié)構(gòu):
Class文件采用一種稱為偽結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)。這種偽結(jié)構(gòu)只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表
無(wú)符號(hào)數(shù):
屬于基本的數(shù)據(jù)類型,無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字,索引引用,數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值
以u(píng)1,u2,u4,u8來(lái)分別代表1個(gè)字節(jié),2個(gè)字節(jié),4個(gè)字節(jié),8個(gè)字節(jié)的無(wú)符號(hào)數(shù)
表:
由多個(gè)無(wú)符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型,所有表基本以_info結(jié)尾

注意:其中展示的順序是不允許被更改的
Magic:
魔數(shù),占用4個(gè)字節(jié),值為0xCAFEBABE
作用:確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件
Minor_version:
?????? 存儲(chǔ)Class文件的版本號(hào)中的次版本號(hào)
Major_version:
?????? 存儲(chǔ)Class文件的版本號(hào)中的主版本號(hào)(每個(gè)大版本,將會(huì)在主版本號(hào)上加1,虛擬機(jī)拒絕運(yùn)行超過其版本號(hào)的Class文件,即被jdk8編譯的文件在jdk7環(huán)境的jvm中無(wú)法運(yùn)行)
常量池計(jì)數(shù):
?????? 占用兩個(gè)字節(jié),從1開始計(jì)數(shù),如果是00 16(16進(jìn)制)表示有21個(gè)常量15+6,如果是00 00表示沒有常量
常量池:
?????? 可以理解為Class文件的資源倉(cāng)庫(kù),占用空間大,第一個(gè)出現(xiàn)表類型的數(shù)據(jù)項(xiàng)目
?????? 主要存放兩大類常量:
字面量(文本字符串,聲明為final的常量值)
符號(hào)引用(類和接口的全限定名,方法的名稱和描述符,字段的名稱和描述符)
常量池中每一項(xiàng)常量都是一個(gè)表,這14中常量類行各自有各自的結(jié)構(gòu),比如CONSTANT_Class_Info結(jié)構(gòu)如下:

Tag是標(biāo)志位,用于區(qū)分常量類行
Name_index是一個(gè)索引值,代表了這個(gè)類或者接口的全限定名
CONTSTANT_Utf8_Info結(jié)構(gòu)如下:

Tag是標(biāo)志位,用于區(qū)分常量類型
Length值說(shuō)明了這個(gè)UTF-8編碼的長(zhǎng)度是多少字節(jié)
Bytes是在length后面緊跟著長(zhǎng)度為length字節(jié)的連續(xù)數(shù)據(jù)
值得注意的是,由于Class文件中方法,字段等都需要使用CONSTANT_Utf8_info型常量來(lái)描述名稱,因此CONSTANT_Utf8_Info的最大長(zhǎng)度也就是我們可以定義的方法,字段的最大長(zhǎng)度(64KB,兩個(gè)字節(jié)2^16)
14中常量項(xiàng)的數(shù)據(jù)結(jié)構(gòu)


訪問標(biāo)志:access_flag
用于識(shí)別一些類或者接口層次的訪問信息

類索引,父類索引與接口索引集合
類索引(this_class,確定該類的全限定名,指向一個(gè)類型為CONSTANT_Class_Info的類描述符常量,通過CONSTATNT_Class_Info類型的常量中的索引值可以找到CONSTANT_Utf8_Info類型的常量中的全限定名字符串)和父類索引(super_class,確定這個(gè)類的父類的全限定名,指向一個(gè)類型為CONSTANT_Class_Info的類描述符常量,通過CONSTATNT_Class_Info類型的常量中的索引值可以找到CONSTANT_Utf8_Info類型的常量中的全限定名字符串)都是一個(gè)u2類型的數(shù)據(jù)
接口索引(interfaces)是一組u2類型的數(shù)據(jù)集合,在這之前是一個(gè)u2類型的數(shù)據(jù)interface_count表示所引表的容量。
字段表集合大?。?/b>一個(gè)u2類型的數(shù)據(jù),表示字段表集合大?。╢ield_count)
字段表集合(field_Info):用于描述接口或者類中聲明的變量,包括類級(jí)變量以及實(shí)例級(jí)變量,但不包括方法內(nèi)聲明的局部變量
字段表集合中單個(gè)field的數(shù)據(jù)結(jié)構(gòu)為

Access_flags中存有字段修飾符,name_index存有字段的簡(jiǎn)單名稱,descriptor_Index存有字段和方法的描述符,后面兩個(gè)都是對(duì)常量池的引用

全限定名:指的是類的className,包括包名
簡(jiǎn)單名稱:指的是沒有類型和參數(shù)修飾的方法和字段名稱,如m字段的簡(jiǎn)單名稱為m,方法inc()的簡(jiǎn)單名稱為inc
方法和字段的描述符:用來(lái)描述字段的數(shù)據(jù)類型,方法的參數(shù)列表(包括數(shù)量,類型以及順序)和返回值
根據(jù)描述符規(guī)則:
基本數(shù)據(jù)類型(boolean,byte,char,short,int,long,float,double)以及代表無(wú)返回值的void類型都用一個(gè)大寫字母表示
對(duì)象類型則用字符L加對(duì)象的全限定名來(lái)表示

對(duì)于數(shù)組類型,每一維將使用一個(gè)前置的“[“字符來(lái)描述,如定義一個(gè)java.lang.String[][]類型的二維數(shù)組,將被記錄為”[[Ljava/lang/String;“,一個(gè)整型數(shù)組”int[]“將被記錄為”[I“
用描述符來(lái)描述方法時(shí),按照先參數(shù)列表,后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴(yán)格順序放在()中,如:
Void inc() 的描述符為()V
Java.lang.String toString()的描述符為()Ljava/lang/String
Int indexOf(char[] source,int
sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int
fromIndex) 的描述符為([CII[CIII)I
方法表集合

與字段表幾個(gè)的access_flags訪問標(biāo)志的可選項(xiàng)中基本類型
刪減了volatile和transient關(guān)鍵字,添加了synchronized,native,strictfp,abstract關(guān)鍵字

方法中的代碼經(jīng)過表義誠(chéng)字節(jié)碼指令后,存放在屬性標(biāo)記和中一個(gè)名為Code的屬性里面,
屬性表集合(attribute_info)
屬性表中會(huì)存在很多屬性標(biāo)簽,相應(yīng)的標(biāo)簽用來(lái)存放相應(yīng)的內(nèi)容,比如上述的Code在方法表集合中用來(lái)存放編譯過后的代碼,ConstantValue用來(lái)表示如果某個(gè)字段是否被聲明為final static,Exception用來(lái)存放方法拋出的異常,InnerCLass內(nèi)部類列表等等
對(duì)于每個(gè)屬性,它的名稱需要從常量池中引用一個(gè)CONSTANT_Utf8_Info類型的常量來(lái)表示,而屬性值的接口則是完全自定義的。

ConstantValue屬性
作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值。對(duì)于非static類型的變量的賦值是在實(shí)例構(gòu)造器<init>方法中進(jìn)行的。對(duì)于類變量,則有兩種方式可以選擇:在類構(gòu)造器<clinit>方法或者使用ConstantValue屬性。
如果同時(shí)使用final和static來(lái)修飾基本類型或者java.lang.String的變量的話,就生成ConstantValue屬性來(lái)初始化。
如果這個(gè)變量不是基本類,字符串或者沒有被final修飾,則將會(huì)選擇在<clinit>方法中進(jìn)行初始化

虛擬機(jī)類加載機(jī)制
虛擬機(jī)把描述類的數(shù)據(jù)從class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型,這就是虛擬機(jī)的類加載機(jī)制。
在java中,類型的加載,連接和初始化過程都是在程序運(yùn)行期間完成的,稱之為運(yùn)行期動(dòng)態(tài)加載和動(dòng)態(tài)連接。
類的生命周期包括:加載,驗(yàn)證,準(zhǔn)備,解析,初始化,使用,卸載。
使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先出發(fā)其初始化;
當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒進(jìn)行過初始化,則需要先出發(fā)其父類的初始化
當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(main方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
Public class superclass{
?????? Static{
????????????? System.out.println(“superclassinit”)
?????? }
?????? Publicstatic int value = 123;
}?????
Public class subclass extends superclass{
?????? Static{
????????????? System,out.println(“subclassinit”);
?????? }
}
Main(){
?????? System.out.println(subclass.value)
}
對(duì)于與靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此通過其子類來(lái)引用父類中定義的靜態(tài)字段,只會(huì)出發(fā)父類的初始化而不會(huì)觸發(fā)子類的初始化。
public class ConstClass {
static {
??????? System.
out.println("ConstClass
init !");
}
public static final String?HELLO_WORLD="hello world";
}
public class NotInitialization {
public static void main(String[] args) {
??????? System.
out.println(ConstClass.HELLO_WORLD);
}
}
結(jié)果沒有輸出ConstClass init ,因?yàn)镠ELLO_WORLD被申明為static final 同時(shí)又是個(gè)String,所以會(huì)被ConstantValue修飾,在編譯階段通過常量傳播優(yōu)化,就已經(jīng)將HELLO_WORLD存儲(chǔ)到了NotInitialzation類的常量池中,以后NotInitialization對(duì)常量ConstClass.HELLO_WORLD的引用實(shí)際都被轉(zhuǎn)化為NotInitialization類對(duì)自身常量池的引用。
如果將HELLO_WORLD的修飾符中final去掉,或者將String換成其他非基本類型,就可以打印出ConstClass init
一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時(shí)候(如引用接口定義的常量)才會(huì)被初始化
加載:
虛擬機(jī)需要完成的一下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ù)的訪問入口。
加載階段完成之后,二進(jìn)制流就存儲(chǔ)在了方法區(qū)之中,作為程序訪問這個(gè)類型數(shù)據(jù)的外部接口
驗(yàn)證:
驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求。
驗(yàn)證階段是非常重要的,這個(gè)階段是否嚴(yán)謹(jǐn),直接決定了java虛擬機(jī)是否能承受惡意代碼的攻擊,從執(zhí)行性能的角度上講,驗(yàn)證階段的工作量在虛擬機(jī)的類加載子系統(tǒng)中又占了相當(dāng)大的一部分。整體上看,驗(yàn)證階段大致上會(huì)完成下面4各階段的校驗(yàn)動(dòng)作:文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證,符號(hào)引用驗(yàn)證。驗(yàn)證失敗的話會(huì)拋出java.lang.cerifyError異常,及其子類異常。
文件格式驗(yàn)證:
這一階段要驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。
驗(yàn)證點(diǎn):
是否以魔數(shù)0xCAFEBABE開頭
主次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)
常量池的常量中是否有不被支持的常量類型
指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量
CONSTANT_UTF8_INFO型的常量中是否有不符合UTF8編碼的數(shù)據(jù)
Class文件中各個(gè)部分及文件本身是否有被刪除或附加的其他信息?????
等等。。。
元數(shù)據(jù)驗(yàn)證:
第二階段是對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證其描述的信息符合java語(yǔ)言規(guī)范的要求,這個(gè)階段可能包括的驗(yàn)證點(diǎn)如下:
除了java.lang.Object以外,所有的類都應(yīng)當(dāng)有父類
這個(gè)類的父類是否繼承了不允許被繼承的類
如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了其父類或接口之中要求實(shí)現(xiàn)的所有方法
類中的字段,方法是否與父類產(chǎn)生矛盾(例如覆蓋了父類的final字段,或者出現(xiàn)了不符合規(guī)則的方法重載,例如方法參數(shù)都一致,但返回值類型卻不同
字節(jié)碼驗(yàn)證:
這一階段是整個(gè)驗(yàn)證過程中最復(fù)雜的一個(gè)階段,主要目的是通過數(shù)據(jù)流和控制流分析,確定程序語(yǔ)義是合法的,符合邏輯的,例如:
保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作,例如不餓能出現(xiàn)類型這樣的情況,在操作棧放置了一個(gè)int類型的數(shù)據(jù),使用時(shí)卻按long類型來(lái)加載入本地變量表中。
保證跳轉(zhuǎn)指令不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上
保證方法體中的類型轉(zhuǎn)換是有效的,錄入可以把一個(gè)子類對(duì)象賦值給父類數(shù)據(jù)類型,這是安全的,但是把父類對(duì)象賦值給子類數(shù)據(jù)類型,甚至把對(duì)象賦值給與它毫無(wú)繼承關(guān)系,完全不相干的一個(gè)數(shù)據(jù)類型,則是危險(xiǎn)和不合法的。
符號(hào)引用驗(yàn)證:
符號(hào)引用驗(yàn)證可以看作是對(duì)類自身以外(常量池中的各種富豪引用)的信息進(jìn)行匹配性校驗(yàn),例如:
符號(hào)引用中通過字符串描述的權(quán)限定名是否能找到對(duì)應(yīng)的類
在指定類中是否存在符合方法的字段描述符以及簡(jiǎn)單名稱所描述的方法和字段
符號(hào)引用中的類,字段,方法的訪問行是否可被當(dāng)前類訪問
對(duì)于虛擬機(jī)的類加載機(jī)制來(lái)說(shuō),驗(yàn)證階段是一個(gè)非常重要的,但不是一定必要的階段。如果所運(yùn)行的全部代碼都已經(jīng)被反復(fù)使用和驗(yàn)證過,那么在實(shí)施階段可以考慮使用–Xverify:none參數(shù)來(lái)關(guān)閉大部分的類驗(yàn)證措施,以縮短虛擬機(jī)類加載的時(shí)間
準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量 初始值的階段。
其中類變量指的是帶有static的變量,
初始值分為兩種,一種變量唄static final修飾,則初始化為代碼中指定的值,如:
Public static final int value= 123
這時(shí)value唄套上ConstantValue屬性,value值為123
如果是:
Public static int value = 123
則初始值為0
解析
是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接應(yīng)用的過程
符號(hào)引用:
符號(hào)引用再class文件中他以CONSTANT_Class_Info,CONSTANT_Fieldref_Info,CONSTANT_Methodref_Info等類型的常量出現(xiàn)。以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,符號(hào)引用的字面量形式明確定義在java虛擬機(jī)規(guī)范的Clas文件格式中。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。
直接引用:
?????? 直接引用可以是直接指向目標(biāo)的指針,相對(duì)偏移量或者是一個(gè)能間接定位到目標(biāo)的句柄。有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。故與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)。
解析動(dòng)作主要針對(duì)

初始化
是類加載過程的最后一步,在準(zhǔn)備階段,變量已經(jīng)賦過一次系統(tǒng)要求的初始值,在初始化階段,則根據(jù)程序員通過程序制定的主觀計(jì)劃去初始化變量和其他資源。初始化階段實(shí)質(zhì)性類構(gòu)造器<clinit>方法的過程
方法是由編譯器自動(dòng)收集類中所有的類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊的語(yǔ)句合并產(chǎn)生的(所以如果沒有,就將不會(huì)產(chǎn)生方法),編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語(yǔ)句塊對(duì)定義在在靜態(tài)語(yǔ)句塊之后的類變量只具有賦值,不具備訪問的能力,否則編譯器會(huì)提示“非法向前引用“
<clinit>方法不需要現(xiàn)實(shí)的調(diào)用父類構(gòu)造器,會(huì)保證在子類的<clinit>方法執(zhí)行之前,父類的<clinit>方法已經(jīng)執(zhí)行完畢
虛擬機(jī)會(huì)保證一個(gè)類的<clinit>方法在多線程環(huán)境中被正確的加鎖,同步,如果多線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)縣城去執(zhí)行這個(gè)類的<clinit>方法,其他線程都需要阻塞等待
類加載器:
對(duì)于任意一個(gè)類,都需要有加載它的類加載器和這個(gè)類本身一同確立其在java虛擬機(jī)中的唯一性,每一個(gè)類加載器都擁有一個(gè)獨(dú)立的類名稱空間。即:比較兩個(gè)類是否相等,只有在這兩個(gè)類是有同一個(gè)類加載器加載的前提下才有意義,否則,即使這兩個(gè)類來(lái)源于同一個(gè)Class文件,,被同一個(gè)虛擬機(jī)加載,只要加載他們的類加載器不同,那這兩個(gè)類就必定不相等。這里所指的相等,包括類的Class對(duì)象的equals方法,isAssignableFrom方法,isInstance方法,也包括使用instanceOf關(guān)鍵字對(duì)象所屬關(guān)系判定等情況。
雙親委派模型:
絕大多數(shù)java程序都會(huì)使用到以下3種系統(tǒng)提供的類加載器:
1:?jiǎn)?dòng)類加載器,這個(gè)類加載器負(fù)責(zé)將存放在JAVA_HOME\lib目錄中,或者被-Xbootclasspath參數(shù)所制定的路徑中,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別,如rt.jar,名字不符合的類庫(kù)即使放在lib目錄中也不會(huì)被加載)
2:擴(kuò)展類加載器,這個(gè)加載器負(fù)責(zé)加載JAVA_HOME\libn\ext目錄中,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù)
3:應(yīng)用程序類加載器:負(fù)責(zé)加載用戶類路徑(ClassPath)上所制定的類庫(kù)

以上圖中的類加載器之間的這種層次關(guān)系,成為類加載器的雙親委派模型。
工作過程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,他首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。
雙親只是parent的直譯,并不代表兩個(gè)父類加載器或者一個(gè)父類加載器一個(gè)母類加載器
雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型。
虛擬機(jī)字節(jié)碼執(zhí)行引擎
代碼編譯的結(jié)果從本地機(jī)器碼轉(zhuǎn)變?yōu)樽止?jié)碼,是編程語(yǔ)言發(fā)展的一大步
運(yùn)行時(shí)棧幀結(jié)構(gòu)
1:棧幀是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),他是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的虛擬機(jī)棧的棧元素。
2:棧幀存儲(chǔ)了方法的局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接和方法返回地址等信息。
3:每一個(gè)方法從調(diào)用開始至執(zhí)行完成的過程,都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里面從入棧到出棧的過程
虛擬機(jī)棧中會(huì)有很多棧幀,處于棧頂?shù)臈硎炯磳?zhí)行或者正在執(zhí)行的方法。如圖:

局部變量表:
1:用于存放方法參數(shù)和方法內(nèi)部定義的局部變量
2:局部變量表的容量以變量槽(slot)為最小單位,但并不規(guī)定slot占用內(nèi)存大小,能存放一個(gè)boolean,byte,char,short,int,float,reference,returnAddress類型的數(shù)據(jù)
3:在方法執(zhí)行時(shí),虛擬機(jī)是使用局部變量表完成參數(shù)值到參數(shù)變量列表的傳遞過程的,如果執(zhí)行的是實(shí)例方法,那局部變量表中第0位索引的slot默認(rèn)是用于傳遞方法所屬對(duì)象實(shí)例的引用
4:對(duì)于變量的賦值問題,
[if !supportLists]1>??[endif]如果是類變量,系統(tǒng)會(huì)由兩次賦值過程,一次是在準(zhǔn)備階段,賦予系統(tǒng)初始值,另外一次是在初始化階段,賦予程序員定義的初始值。
[if !supportLists]2>??[endif]如果是局部變量,如果一個(gè)局部變量定義了但沒有賦初始值是不能使用的。
[if !supportLists]3>??[endif]所以,虛擬機(jī)并不是會(huì)為每一個(gè)變量都賦予初始值
操作數(shù)棧:
操作數(shù)棧中存訪的是數(shù)據(jù),例如
1:計(jì)算a+b=c,首先會(huì)將a和b放入操作數(shù)棧,然后將兩個(gè)值取出來(lái),進(jìn)行加法運(yùn)算后,再將c寫入操作數(shù)棧。
2:調(diào)用其它方法的時(shí)候是通過操作數(shù)棧來(lái)進(jìn)行參數(shù)傳遞的
所以,在方法開始執(zhí)行的時(shí)候,這個(gè)方法的操作數(shù)棧是空的,在執(zhí)行的過程中會(huì)有各種指令對(duì)操作數(shù)棧中進(jìn)行寫入和提取數(shù)據(jù),也就是出棧/入棧操作。
動(dòng)態(tài)鏈接:
每個(gè)棧幀(線程->虛擬機(jī)棧->棧幀)都包含一個(gè)指向運(yùn)行常量池(虛擬機(jī)內(nèi)存->方法區(qū)->運(yùn)行時(shí)常量池)中該棧幀所屬方法的引用。
Class文件的常量池中存有大量的符號(hào)引用,這些符號(hào)引用一部分會(huì)在類加載階段或者第一次使用的時(shí)候就轉(zhuǎn)換為直接引用,這種轉(zhuǎn)化成為靜態(tài)解析。
另外一部分將在每一次運(yùn)行期間轉(zhuǎn)化為直接引用,這部分稱為動(dòng)態(tài)鏈接
方法返回地址:
用于方法執(zhí)行過程中出現(xiàn)異常退出或者執(zhí)行完成后返回上級(jí)方法,并恢復(fù)上級(jí)方法的執(zhí)行狀態(tài)(局部變量表和操作數(shù)棧,并把返回值(有的話)壓入調(diào)用者站真的操作數(shù)棧中,調(diào)整程序計(jì)數(shù)器的值以指向后面的一條指令)
方法調(diào)用:
Class文件編譯過后,在class文件的常量池中會(huì)存放方法的符號(hào)引用
在類加載階段(解析階段),會(huì)將其中一部分符號(hào)引用轉(zhuǎn)化為直接引用,但并不包括所有的方法都會(huì)在解析階段被轉(zhuǎn)化為直接引用,需要符合“編譯器可知,運(yùn)行時(shí)不可變”的原則。符合這個(gè)原則的是
?????? 1:invokestatic:靜態(tài)方法
?????? 2:invokespecial:實(shí)例構(gòu)造器<init>,私有方法和父類方法
?????? 3:invokevirtual:所有的虛方法
?????? 4:invokeinterface:調(diào)用接口方法,會(huì)在運(yùn)行時(shí)在確定一個(gè)實(shí)現(xiàn)此接口的對(duì)象
?????? 5:invokedynamic:現(xiàn)在運(yùn)行時(shí)動(dòng)態(tài)解析調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法
Tomcat中java類庫(kù)放置的目錄路徑及其作用范圍:
1:放置在common目錄中的,類庫(kù)可悲tomcat和所有的Web應(yīng)用程序共同使用,對(duì)應(yīng)的類加載器為:CommonClassLoader
2:放置在server目錄中,類庫(kù)可被tomcat使用,對(duì)所有的web應(yīng)用程序都不可見,對(duì)應(yīng)的類加載器為:CatalinaClassLoader
3:放置在shared目錄中,類庫(kù)可被所有的Web應(yīng)用程序共同使用,但對(duì)tomcat是不可見的,對(duì)應(yīng)的類加載器為:SharedClassLoader
4:放置在/webapp/WEN-INF目錄中:類庫(kù)僅僅可被web應(yīng)用程序使用,對(duì)tomcat和其他的web應(yīng)用程序都不可見,對(duì)應(yīng)的類加載器為:WebAppClassLoader
類加載流程圖:

Java內(nèi)存模型與線程
C/C++等語(yǔ)言直接使用物理硬件和操作系統(tǒng)提供的內(nèi)存模型,因此會(huì)由于不同平臺(tái)上的內(nèi)存模型的差異,有可能導(dǎo)致程序在一套平臺(tái)上并發(fā)完全正常,而在另外一套平臺(tái)上并發(fā)訪問卻經(jīng)常出錯(cuò),因此C/C++在某些場(chǎng)景下就必須針對(duì)不同的平臺(tái)來(lái)編寫程序。
Java試圖提供一種內(nèi)存模型,使得代碼層面無(wú)需考慮各種硬件和操作系統(tǒng)的內(nèi)存訪問差異
Java中每個(gè)工作線程都有一個(gè)獨(dú)立的工作內(nèi)存,每個(gè)工作內(nèi)存存放著本線程執(zhí)行時(shí)需要的變量等信息,并且線程之間是不能共享變量值的。Java的內(nèi)存模型主要解決“一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存,如何從工作內(nèi)存同步到主內(nèi)存之類的實(shí)現(xiàn)原理”

Java內(nèi)存模型中定義了一下8種操作來(lái)完成變量在工作內(nèi)存與主內(nèi)存之間的傳遞。
Lock 鎖定,作用于主內(nèi)存的變量,他把一個(gè)變量標(biāo)志位一條線程獨(dú)占的狀態(tài)
Unlock 解鎖作用于主內(nèi)存的變量,他把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定
Read?作用于主內(nèi)存的變量,他把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中
Load作用于工作內(nèi)存的變量,他把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中
Use作用于工作內(nèi)存的變量,他把工作內(nèi)存的一個(gè)變量的值傳遞給執(zhí)行引擎
Assign?作用于工作內(nèi)存的變量,執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量
Store?作用于工作內(nèi)存的變量,他把工作內(nèi)存中一個(gè)變量傳送到主內(nèi)存中
Write?作用于主內(nèi)存的變量,他把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中
Volatile變量修飾符
當(dāng)一個(gè)變量定義為volatile之后,他將具備兩種特性,
第一是保證此變量對(duì)所有線程的可見性,這里的可見性是指當(dāng)一條線程改變了這個(gè)變量的值,新值對(duì)于其他線程是可以立即得知的。而普通變量則需要通過主內(nèi)存與工作內(nèi)存之間的變量值的傳遞來(lái)完成。但并不代表使用volatile在多線程環(huán)境中就一定是安全的,要保證安全,還是得靠synchronized或者java.util.concurrent的原子類(volatile具備可見性,但并不具備原子性)
第二是可以禁止指令重排序,普通變量?jī)H僅會(huì)保證在該方法的執(zhí)行過程中所有以來(lái)復(fù)制結(jié)果的地方都能獲得正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼的執(zhí)行順序一致。作用方式是通過在使用volatile變量的地方添加了內(nèi)存屏障,在重排序時(shí)不能把后頁(yè)面的指令重排序到內(nèi)存屏障之前的位置
Java內(nèi)存模型是圍繞在并發(fā)過程中如何處理原子性可見性和有序性這3個(gè)特征來(lái)建立的:
原子性:
由java內(nèi)存模型來(lái)直接保證的原子性變量操作包括read,load,assign,use,store,write
可見性:
可以使用volatile,final,synhronized關(guān)鍵字來(lái)實(shí)現(xiàn)可見性
有序性:
如果在線程內(nèi)觀察,所有的操作都是有序的(線程內(nèi)表現(xiàn)為串行);如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無(wú)序的(指令重排序和工作內(nèi)存與主內(nèi)存同步延遲)。
Java通過提供volatile和synchronized兩個(gè)關(guān)鍵字來(lái)保證線程之間操作的有序性。
Volatile本身就包含了禁止指令重排序的語(yǔ)義
Synchronized則是由“一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”