《LINUX入門:《深入理解Java虛擬機(jī)》 讀書筆記》要點(diǎn):
本文介紹了LINUX入門:《深入理解Java虛擬機(jī)》 讀書筆記,希望對(duì)您有用。如果有疑問,可以聯(lián)系我們。
Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)
?
Java創(chuàng)建對(duì)象,在語(yǔ)言層面上使用new關(guān)鍵字.虛擬機(jī)遇到new關(guān)鍵字時(shí),會(huì)檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已經(jīng)被加載、解析和初始化過(guò).如果沒有,那就必須先執(zhí)行類加載過(guò)程.類加載通過(guò)之后,虛擬機(jī)將會(huì)為新生對(duì)象分配內(nèi)存.對(duì)象所需的內(nèi)存在類加載完成后就能完全確定.分配內(nèi)存的辦法有“指針碰撞”和“空閑列表”兩種方式,如果Java堆是規(guī)整的,則采用前者;否則,采用后者.Java堆是否規(guī)則和虛擬機(jī)有關(guān).
深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐 第2版 高清PDF+源碼?
在虛擬機(jī)中,能夠發(fā)生OOM的有:虛擬機(jī)棧,本地辦法棧,Java堆,辦法區(qū),運(yùn)行時(shí)常量.分析OOM使用eclipse memory analyzer插件或者JProfiler工具.添加jvm參數(shù)(-XX:+PrintGCDetails)可以打印出GC的詳細(xì)日志,-Xloggc:gc.log 日志文件的輸出路徑.
幾乎所有的對(duì)象的視力都在這里分配內(nèi)存.通過(guò)參數(shù)-Xmx(JVM最大可用內(nèi)存)和-Xms(JVM初始可用內(nèi)存)控制對(duì)的大小.如果在堆中沒有內(nèi)存完成實(shí)例分配并且堆無(wú)法擴(kuò)展,就會(huì)OOM.棧-Xss設(shè)置棧的容量大小.HotSpot不區(qū)分本地辦法棧和虛擬機(jī)棧.如果虛擬機(jī)在申請(qǐng)棧擴(kuò)展時(shí),沒有足夠的空間,則會(huì)OOM.
判斷對(duì)象是否存活,兩種方式:引用計(jì)數(shù)法和可達(dá)性分析.
引用計(jì)數(shù)法,不能辦理對(duì)象相互引用的情況,所以主流虛擬機(jī)都采用的是可達(dá)性分析.
可達(dá)性分析采用采用GCROOTS策略,可作為GCROOTS的有:
?
虛擬機(jī) | 新生代 | 老年代 | 垃圾算法 | 備注 |
---|---|---|---|---|
Serial | ? | ? | 復(fù)制算法 | ? |
ParNew | ? | ? | 復(fù)制算法 | ? |
Parallel Scavenge | ? | ? | 復(fù)制算法 | ? |
Serial Old | ? | ? | 標(biāo)志-整理算法 | ? |
Parallel Old | ? | ? | 標(biāo)志-整理算法 | ? |
CMS | ? | ? | 標(biāo)志-清除算法 | ? |
G1 | ? | ? | 綜合算法 | 目前最先進(jìn)的垃圾回收器 |
下面是一次GC產(chǎn)生的日志(jvm參數(shù)-XX:+PrintGCDetails):
從日志中可以看到,System.gc()表示程序調(diào)用了System.gc()辦法觸發(fā)了垃圾回收,[PSYoungGen:3932k->592k(76288k)]中PSYoungGen代表了GC發(fā)生的區(qū)域,這個(gè)名稱和GC收集器密切相關(guān):如果顯示的是[DefNew,則表明是Serial收集器的新生代;如果是ParNew則表明是ParNew收集器的新生代;我們這里是PSYoungGen,則表明是Parallel Scavenge收集器的新生代.再看后面是數(shù)字,3932k->592k(76288k)代表的是:GC前該內(nèi)存已經(jīng)使用的容量->GC后該內(nèi)存區(qū)域使用的容量(該內(nèi)存區(qū)域總?cè)萘?.3932K->600K(251392K)表示GC前Java堆已經(jīng)使用的容量->GC后Java堆已經(jīng)使用的容量(Java堆總?cè)萘?.后面的時(shí)間表示GC所花費(fèi)的時(shí)間,單位s.?
大多數(shù)情況下,對(duì)象首先會(huì)被分配到Eden區(qū),當(dāng)Eden區(qū)滿了,會(huì)觸發(fā)一次Minor GC.
所謂的大對(duì)象是指,必要大量連續(xù)內(nèi)存空間的Java對(duì)象,最典型的大對(duì)象就是那種很長(zhǎng)的字符串以及數(shù)組(筆者列出的例子中的byte[]數(shù)組就是典型的大對(duì)象).虛擬機(jī)提供了一個(gè)-XX:PretenureSizeThreshold參數(shù),令大于這個(gè)設(shè)置值的對(duì)象直接在老年代分配.這樣做的目的是避免在Eden區(qū)及兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制(復(fù)習(xí)一下:新生代采用復(fù)制算法收集內(nèi)存).
對(duì)象在Survivor區(qū)中每“熬過(guò)”一次Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到必定程度(默認(rèn)為15歲),就將會(huì)被晉升到老年代中.對(duì)象晉升老年代的年齡閾值,可以通過(guò)參數(shù)-XX:MaxTenuringThreshold設(shè)置.
虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須比及MaxTenuringThreshold中要求的年齡.
在發(fā)生Minor GC之前,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那么Minor GC可以確保是平安的.如果不成立,則虛擬機(jī)會(huì)查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗.如果允許,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試著進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險(xiǎn)的,如果擔(dān)保失敗則會(huì)進(jìn)行一次Full GC;如果小于,或者HandlePromotionFailure設(shè)置不允許冒險(xiǎn),那這時(shí)也要改為進(jìn)行一次Full GC.
jps格式:jps [options] [hostid]
功能:列出正在執(zhí)行的虛擬機(jī)進(jìn)程,并顯示虛擬機(jī)執(zhí)行主類(Main Class,main()函數(shù)所在的類)名稱以及這些進(jìn)程的當(dāng)?shù)靥摂M機(jī)唯一ID(Local Virtual Machine Identifier,LVMID).
選項(xiàng) | 作用 |
---|---|
-q | 只輸出LVMID,省略主類的名稱 |
-m | 輸出虛擬機(jī)進(jìn)程啟動(dòng)時(shí)傳給主類main()的參數(shù) |
-l | 輸出主類的全名,如果主類執(zhí)行的是jar,則輸出jar的路徑 |
-v | 輸出虛擬機(jī)進(jìn)程啟動(dòng)時(shí)傳給jvm的參數(shù) |
功能:它可以顯示當(dāng)?shù)鼗蛘哌h(yuǎn)程[1]虛擬機(jī)進(jìn)程中的類裝載、內(nèi)存、垃圾收集、JIT編譯等運(yùn)行數(shù)據(jù).
命令格式:jstat [option vmid [ interval [s|ms] [count] ] ],參數(shù)interval和count代表查詢間隔和次數(shù).
其中,如果進(jìn)程是當(dāng)?shù)靥摂M機(jī)進(jìn)程,則vmid與lvmid一致;如果是遠(yuǎn)程虛擬機(jī)進(jìn)程,則vmid格式為:[protocol:][//]lvmid[@hostname[:port]/servername]
舉例:
S0代表Survivor0;S1代表Survivor1;E代表Eden區(qū);O代表Old區(qū);M代表Metaspace元數(shù)據(jù)區(qū)域(JDK1.8用Metaspace代替了1.7以前的PermGen永久代);CCS表示的是NoKlass Metaspace的使用率也便是CCSU,/CCSC算出來(lái)的,具體可參看這里,總之在1.7之前,M所在的位置是P,表示永久代使用的比例;YGC代表程序運(yùn)行以來(lái)共發(fā)生Minor GC;YGCT表示其所用的時(shí)間;FGC代表程序運(yùn)行以來(lái)共發(fā)生Full GC;FGCT表示其所用的時(shí)間;GCT表示GC總共花費(fèi)了所長(zhǎng)時(shí)間.
格式:jinfo [option] pid
作用:實(shí)時(shí)的查看和調(diào)整java虛擬機(jī)各項(xiàng)參數(shù).
格式:jmap [option] vmid
作用:用于生成堆轉(zhuǎn)儲(chǔ)快照(一般稱為heapdump或dump文件).
選項(xiàng) | 作用 |
---|---|
-dump | 生成Java堆轉(zhuǎn)儲(chǔ)快照.格式:-dump:[live,] format -b,file=<filename>,其中l(wèi)ive字參數(shù)說(shuō)明是否只dump存活的對(duì)象. |
-heap | 顯示Java堆詳細(xì)信息,只在Linux/Solaris平臺(tái)下有效 |
-histo | 顯示Java堆中對(duì)象統(tǒng)計(jì)信息 |
-F | 當(dāng)虛擬機(jī)對(duì)-dump選項(xiàng)沒有響應(yīng)時(shí),該參數(shù)可以強(qiáng)制生成dump快照.只在Linux/Solaris平臺(tái)下有效 |
作用:配合jmap命令,分析dump文件.jhat內(nèi)置了一個(gè)微型的HTTP/HTML服務(wù)器,生成dump文件的分析結(jié)果可以在瀏覽器中查看.
命令:jhat dumpfile
一般使用專業(yè)的工具進(jìn)行分析,例如:VisualVM,Eclipse Memory Analyzer,IBM HeapAnalyzer等.
格式:jstack [option] vmid
作用:用于生成虛擬機(jī)當(dāng)前時(shí)刻的線程快照,生成線程快照的主要目的是定位線程出現(xiàn)長(zhǎng)時(shí)間停頓的原因,如線程間死鎖、死循環(huán)、哀求外部資源導(dǎo)致的長(zhǎng)時(shí)間等待等都是導(dǎo)致線程長(zhǎng)時(shí)間停頓的常見原因.線程出現(xiàn)停頓的時(shí)候通過(guò)jstack來(lái)查看各個(gè)線程的調(diào)用堆棧,就可以知道沒有響應(yīng)的線程到底在后臺(tái)做些什么事情,或者等待著什么資源.
任何語(yǔ)言,不局限于Java,都可以在虛擬機(jī)上運(yùn)行.Java虛擬機(jī)不care .class文件來(lái)自何種語(yǔ)言.
Class文件是一組以8位字節(jié)為基??單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格依照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符
根據(jù)Java虛擬機(jī)規(guī)范規(guī)定,Class文件中采用類似C語(yǔ)言結(jié)構(gòu)體中的偽結(jié)構(gòu)體來(lái)存儲(chǔ)數(shù)據(jù),這種結(jié)構(gòu)體中只存在兩種數(shù)據(jù)結(jié)構(gòu):無(wú)符號(hào)數(shù)和表.
每個(gè)Class文件的頭4個(gè)字節(jié)被稱為魔數(shù),它的唯一作用是確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件.Class文件的魔數(shù)為:0xCAFFEEBABE.緊接著魔數(shù)的4個(gè)字節(jié)存儲(chǔ)的是Class文件的版本號(hào):第5和第6個(gè)字節(jié)是次版本號(hào)(Minor Version),第7和第8個(gè)字節(jié)是主版本號(hào)(Major Version).Java的版本號(hào)是從45開始的,JDK 1.1之后的每個(gè)JDK大版本發(fā)布主版本號(hào)向上加1(JDK 1.0~1.1使用了45.0~45.3的版本號(hào)),高版本的JDK能向下兼容以前版本的Class文件,但不能運(yùn)行以后版本的Class文件,即使文件格式并未發(fā)生任何變化,虛擬機(jī)也必須拒絕執(zhí)行超過(guò)其版本號(hào)的Class文件.?
緊接著主版本號(hào)后面的是常量池入口.
更多詳情見請(qǐng)繼續(xù)閱讀下一頁(yè)的精彩內(nèi)容:
_baidu_page_break_tag_?
上圖所示,類加載過(guò)程包括:加載,驗(yàn)證,準(zhǔn)備,解析,初始化,使用,卸載.其中加載,驗(yàn)證,準(zhǔn)備,初始化,卸載這5個(gè)階段的順序是確定.而解析階段不一定,某些時(shí)候可以在初始化之后才進(jìn)行解析,這是為了支持Java中動(dòng)態(tài)綁定.什么時(shí)候開始類加載的一個(gè)過(guò)程:加載?虛擬機(jī)規(guī)范中沒有嚴(yán)格的約束,交給虛擬機(jī)的實(shí)現(xiàn)去具體把握.但是對(duì)于初始化,虛擬機(jī)規(guī)范中嚴(yán)格規(guī)定了5中場(chǎng)景必須進(jìn)行初始化:
以上5種場(chǎng)景稱為主動(dòng)引用,會(huì)首先進(jìn)行初始化,除此之外,所有引用類的方式都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用.
被動(dòng)引用例子一:
1 public class SuperClass { 2 3 public static int value = 123; 4 5 static{ 6 System.out.println("SuperClass init !"); 7 } 8 } 9 10 public class SubClass extends SuperClass { 11 12 static{ 13 System.out.println("SubClass init !"); 14 } 15 } 16 17 public class TestInit { 18 public static void main(String[] args) { 19 System.out.println(SubClass.value); 20 } 21 }
以上執(zhí)行的結(jié)果如下:
為什么只會(huì)打印出"SuperClass init !"?因?yàn)閷?duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此通過(guò)其子類來(lái)引用父類中定義的靜態(tài),只會(huì)觸發(fā)父類的初始化而不會(huì)觸發(fā)子類的初始化.
被動(dòng)引用例子二:
public class ConstClass { static { System.out.println("ConstClass Init ~~"); } public final static String HELLO_STRING = "HelloString"; } public class TestConst{ public static void main(String[] args) { System.out.println(ConstClass.HELLO_STRING); } }
以上執(zhí)行結(jié)果也沒有輸出ConstClass Init ~~,原因是常量在編譯階段會(huì)存入調(diào)用類的常量池中,本色上并沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化.
“加載”是“類加載”過(guò)程中的一個(gè)階段.在加載階段,虛擬機(jī)會(huì)做3件事:
這三點(diǎn)并不具體,例如第一點(diǎn),Java虛擬機(jī)規(guī)范并沒有指定二進(jìn)制字節(jié)流要從哪一個(gè)Class文件中獲取,怎么去獲取.所以Java可以從:
驗(yàn)證是連接階段的第一步.目的是確保Class文件的字節(jié)流中包含的信息不會(huì)危害到虛擬機(jī)自身的平安.包含:文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證,符號(hào)引用驗(yàn)證.
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在辦法區(qū)中進(jìn)行分配.其中初始值“通常情況下”是數(shù)據(jù)類型的零值.假設(shè)一個(gè)類變量的定義為:
public static int value = 123;
那變量在準(zhǔn)備階段過(guò)后的初始值是0而不是123,因?yàn)檫@時(shí)候尚未進(jìn)行任何的Java辦法,而把value賦值為123是在初始化階段.但是如果上面的類變量定義為:
public static final int value = 123;
那么在編譯階段javac會(huì)為value生成ConstantValue屬性,而且在準(zhǔn)備階段value就會(huì)被賦值為123.
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程.
類初始化階段是類加載的最后一步,初始化階段是執(zhí)行類構(gòu)造器<clinit>()辦法的過(guò)程,<clinit>()辦法是由編譯器自動(dòng)收集類中所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊中的語(yǔ)句合并產(chǎn)生的.
<clinit>()辦法與類的構(gòu)造函數(shù)<init>()不同,它不需要顯示的調(diào)用父類構(gòu)造器,虛擬機(jī)會(huì)保證在子類的<clinit>()辦法執(zhí)行之前,父類的<clinit>()辦法已經(jīng)執(zhí)行完畢.所以在虛擬機(jī)中第一個(gè)被執(zhí)行的<clinit>()辦法的類必定是java.lang.Object.
<clinit>()對(duì)于類或接口來(lái)說(shuō)并不是必須的,如果類中沒有靜態(tài)語(yǔ)句塊,也沒有對(duì)變量的賦值操作,那么編譯器不會(huì)生成該辦法.
接口中不能使用靜態(tài)語(yǔ)句塊,但仍然有變量初始化的賦值操作,因此接口也會(huì)生成<clinit>().但接口和類不同的是:執(zhí)行接口的<clinit>()不必要先執(zhí)行父接口的<clinit>(),只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)初始化.另外接口的實(shí)現(xiàn)類在初始化也一樣不會(huì)執(zhí)行接口的<clinit>()
對(duì)于任意一個(gè)類,都需要由他的類加載器和這個(gè)類自己共同確立其在Java虛擬機(jī)中的唯一性.每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間.簡(jiǎn)言之,比較兩個(gè)類是否“相等”只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義.
如下例子:
1 /** 2 * @author zhouxuanyu 3 * @date 2017/06/03 4 */ 5 public class ClassLoaderTest { 6 public static void main(String[] args) { 7 ClassLoader myLoader = new ClassLoader() { 8 @Override 9 public Class<?> loadClass(String name) throws ClassNotFoundException { 10 try { 11 String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; 12 InputStream is = getClass().getResourceAsStream(fileName); 13 14 if (is == null) { 15 return super.loadClass(name); 16 } 17 byte[] b = new byte[is.available()]; 18 is.read(b); 19 return defineClass(name, b, 0, b.length); 20 } catch (IOException e) { 21 throw new ClassNotFoundException(); 22 } 23 } 24 }; 25 26 try { 27 Object o = myLoader.loadClass("com.alibaba.jvm.ClassLoaderTest").newInstance(); 28 System.out.println(o.getClass()); 29 System.out.println(o instanceof com.alibaba.jvm.ClassLoaderTest); 30 } catch (InstantiationException e) { 31 e.printStackTrace(); 32 } catch (IllegalAccessException e) { 33 e.printStackTrace(); 34 } catch (ClassNotFoundException e) { 35 e.printStackTrace(); 36 } 37 } 38 }
最后的運(yùn)行結(jié)果為:
1 class com.alibaba.jvm.ClassLoaderTest 2 false
可以看出,我們本身實(shí)現(xiàn)的類加載器確實(shí)加載并實(shí)例化了類com.alibaba.jvm.ClassLoaderTest的,但是實(shí)例化對(duì)象在與類com.alibaba.jvm.ClassLoaderTest做類型檢查的時(shí)候卻返回了false.因?yàn)樘摂M機(jī)中存在兩個(gè)com.alibaba.jvm.ClassLoaderTest類,一個(gè)是系統(tǒng)類加載器加載,另一個(gè)是由我們本身實(shí)現(xiàn)的加載器加載的,雖然這兩個(gè)類來(lái)自同一個(gè)Class文件但是卻是兩個(gè)獨(dú)立的類.
從Java虛擬機(jī)的角度來(lái)講,只存在兩種不同的類加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器使用C++語(yǔ)言實(shí)現(xiàn),是虛擬機(jī)自身的一部分;另一種便是所有其他的類加載器,這些類加載器都由Java語(yǔ)言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類java.lang.ClassLoader.
從Java開發(fā)人員的角度來(lái)看,有三種類加載器:
啟動(dòng)類加載器(Bootstrap ClassLoader):負(fù)責(zé)加載<java_home>\lib目錄或者由參數(shù)-Xbootclasspath指定路徑中并且是虛擬機(jī)辨認(rèn)的類庫(kù)加載到虛擬機(jī)內(nèi)存中.
擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)加載<java_home>\lib\ext目錄中或者被java.ext.dirs系統(tǒng)變量指定路徑中所有的類庫(kù).
應(yīng)用程序加載器(Application ClassLoader):負(fù)責(zé)加載由CLASSPATH指定的類庫(kù),如果程序沒有自定義類加載器,程序默認(rèn)使用該加載器.
下圖是類加載器的雙親委派模型:
雙親委派模型的工作過(guò)程是:如果一個(gè)類加載器收到了類加載的哀求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)哀求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載哀求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載哀求(它的搜索范圍中沒有找到所需的類)時(shí),子加載器才會(huì)嘗試自己去加載.
Q:為什么要采用雙親委派模型?
A:例如類java.lang.Object,它存放在rt.jar之中,無(wú)論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類.相反,如果沒有使用雙親委派模型,由各個(gè)類加載器自行去加載的話,如果用戶本身編寫了一個(gè)稱為java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無(wú)法保證,應(yīng)用程序也將會(huì)變得一片混亂.
實(shí)現(xiàn)雙親委派的代碼都集中在java.lang.ClassLoader的loadClass()辦法之中,邏輯如下:先檢查是否已經(jīng)被加載過(guò),若沒有加載則調(diào)用父加載器的loadClass()辦法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器.如果父類加載失敗,拋出ClassNotFoundException異常后,再調(diào)用自己的findClass()辦法進(jìn)行加載.下面是loadClass()辦法:
1 protected Class<?> loadClass(String name, boolean resolve) 2 throws ClassNotFoundException 3 { 4 synchronized (getClassLoadingLock(name)) { 5 // First, check if the class has already been loaded 6 Class<?> c = findLoadedClass(name); 7 if (c == null) { 8 long t0 = System.nanoTime(); 9 try { 10 if (parent != null) { 11 c = parent.loadClass(name, false); 12 } else { 13 c = findBootstrapClassOrNull(name); 14 } 15 } catch (ClassNotFoundException e) { 16 // ClassNotFoundException thrown if class not found 17 // from the non-null parent class loader 18 } 19 20 if (c == null) { 21 // If still not found, then invoke findClass in order 22 // to find the class. 23 long t1 = System.nanoTime(); 24 c = findClass(name); 25 26 // this is the defining class loader; record the stats 27 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 28 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 29 sun.misc.PerfCounter.getFindClasses().increment(); 30 } 31 } 32 if (resolve) { 33 resolveClass(c); 34 } 35 return c; 36 } 37 }
棧幀是用于支持虛擬機(jī)進(jìn)行辦法調(diào)用和辦法執(zhí)行的數(shù)據(jù)結(jié)構(gòu).棧幀存儲(chǔ)了辦法的局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和辦法返回地址等信息.每一個(gè)辦法從調(diào)用開始到執(zhí)行完成的過(guò)程都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里面從入棧到出棧的過(guò)程.
局部變量表是一組變量值存儲(chǔ)空間,用于存放辦法參數(shù)和辦法內(nèi)部定義的局部變量.Java程序編譯為Class文件時(shí),就在辦法的Code屬性的max_locals數(shù)據(jù)項(xiàng)中確定了該辦法所需要分配的局部變量表的最大容量.局部變量表的銅梁以變量槽(Slot)為最小單位,Java虛擬機(jī)規(guī)范說(shuō)一個(gè)Slot應(yīng)該能存放一個(gè)boolean,byte,char,short,int,floot,reference以及returnAddress類型的數(shù)據(jù).為了節(jié)省空間,Slot是可以重用的.
辦法調(diào)用不等同于辦法執(zhí)行.辦法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用辦法的版本,即調(diào)用哪一個(gè)辦法,不涉及辦法內(nèi)部的具體運(yùn)行過(guò)程.
所有辦法在調(diào)用中的目標(biāo)辦法在Class文件中都是一個(gè)常量池中的符號(hào)引用,在類加載期間,會(huì)將其中部分符號(hào)引用轉(zhuǎn)化為直接引用.這類辦法必須滿足:辦法在程序真正運(yùn)行之前就有一個(gè)可確定的調(diào)用版本,并且這個(gè)版本在運(yùn)行期間是不可改變的.
在Java中符合“編譯器可知,運(yùn)行期間不可變” 這個(gè)要求的辦法主要包括靜態(tài)辦法和私有辦法.因?yàn)檫@兩類辦法都不能被繼承或重寫.在JVM中,凡是能被invokestatic和invokespecial指令調(diào)用的辦法都可以在解析階段唯一確定調(diào)用版本,符合這個(gè)條件的辦法有:靜態(tài)辦法,私有辦法,實(shí)例構(gòu)造辦法,父類辦法4類.
解析調(diào)用必定是一個(gè)靜態(tài)的過(guò)程,在編譯器就完全的確定,不會(huì)延遲到運(yùn)行期間再去完成.
服務(wù)器都會(huì)辦理幾個(gè)問題:部署在同一個(gè)服務(wù)器上的兩個(gè)web應(yīng)用程序所使用的java類庫(kù)可以實(shí)現(xiàn)相互隔離以及相互共享,服務(wù)器自身的類庫(kù)應(yīng)該與應(yīng)用程序的類庫(kù)隔離.
解決辦法:服務(wù)器會(huì)提供好幾個(gè)classpath路徑供用戶存放第三方類庫(kù),被放置到不同路徑中的類庫(kù),具備不同的拜訪范圍和服務(wù)對(duì)象.通常,每個(gè)目錄都會(huì)有一個(gè)相應(yīng)的自定義類加載器去加載放在里面的Java類庫(kù).以tomcat為例:tomcat目錄結(jié)構(gòu)中,有4組目錄(/common/*,/server/*,/shared/*,/WEB-INF/*)可以存放Java類庫(kù):
本文永久更新鏈接地址:
維易PHP培訓(xùn)學(xué)院每天發(fā)布《LINUX入門:《深入理解Java虛擬機(jī)》 讀書筆記》等實(shí)戰(zhàn)技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養(yǎng)人才。
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.snjht.com/jiaocheng/12974.html