深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第2版)
周志明
JVM規(guī)范讓每個(gè)Java線程擁有自己的獨(dú)立的JVM棧施禾,也就是Java方法的調(diào)用棧
內(nèi)存區(qū)域圖:
2.4.2 虛擬機(jī)棧和本地方法棧溢出
由于在HotSpot虛擬機(jī)中并不區(qū)分虛擬機(jī)棧和本地方法棧,因此倒彰,對(duì)于HotSpot來(lái)說(shuō)今缚,雖然-Xoss參數(shù)(設(shè)置本地方法棧大小)存在豪嗽,但實(shí)際上是無(wú)效的泥畅,棧容量只由-Xss參數(shù)設(shè)定。關(guān)于虛擬機(jī)棧和本地方法棧凭豪,在Java虛擬機(jī)規(guī)范中描述了兩種異常:
如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度焙蹭,將拋出StackOverflowError異常。
如果虛擬機(jī)在擴(kuò)展棧時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存空間嫂伞,則拋出OutOfMemoryError異常孔厉。
這里把異常分成兩種情況
Class文件中除了有類的版本、字段末早、方法烟馅、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table)然磷,用于存放編譯期生成的各種字面量和符號(hào)引用郑趁,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
2.2.4 Java堆
對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō)姿搜,Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊寡润。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域捆憎,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例梭纹,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存躲惰。這一點(diǎn)在Java虛擬機(jī)規(guī)范中的描述是:所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配?,但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)逐漸成熟变抽,棧上分配础拨、標(biāo)量替換?優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化發(fā)生,所有的對(duì)象都分配在堆上也漸漸變得不是那么“絕對(duì)”了绍载。
2.2.5 方法區(qū)
方法區(qū)(Method Area)與Java堆一樣诡宗,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息击儡、常量塔沃、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)阳谍。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分蛀柴,但是它卻有一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來(lái)矫夯。
對(duì)于習(xí)慣在HotSpot虛擬機(jī)上開發(fā)鸽疾、部署程序的開發(fā)者來(lái)說(shuō),很多人都更愿意把方法區(qū)稱為“永久代”(Permanent Generation)茧痒,本質(zhì)上兩者并不等價(jià)肮韧,僅僅是因?yàn)镠otSpot虛
2.2.6 運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中除了有類的版本旺订、字段辅斟、方法认境、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table)藕帜,用于存放編譯期生成的各種字面量和符號(hào)引用意乓,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放樱调。
Java虛擬機(jī)對(duì)Class文件每一部分(自然也包括常量池)的格式都有嚴(yán)格規(guī)定,每一個(gè)字節(jié)用于存儲(chǔ)哪種數(shù)據(jù)都必須符合規(guī)范上的要求才會(huì)被虛擬機(jī)認(rèn)可届良、裝載和執(zhí)行笆凌,但對(duì)于運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池是方法區(qū)的一部分
JIT:即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
1.全局常量池在每個(gè)VM中只有一份,存放的是字符串常量的引用值士葫。
2.class常量池是在編譯的時(shí)候每個(gè)class都有的乞而,在編譯階段,存放的是常量的符號(hào)引用慢显。
3.運(yùn)行時(shí)常量池是在類加載完成之后爪模,將每個(gè)class常量池中的符號(hào)引用值轉(zhuǎn)存到運(yùn)行時(shí)常量池中欠啤,也就是說(shuō),每個(gè)class都有一個(gè)運(yùn)行時(shí)常量池屋灌,類在解析之后洁段,將符號(hào)引用替換成直接引用,與全局常量池中的引用值保持一致共郭。
常量池(constant_pool)存儲(chǔ)了諸如符號(hào)常量祠丝、final常量值、基本數(shù)據(jù)類型的字面值等內(nèi)容除嘹。
2.3.3 對(duì)象的訪問(wèn)定位
(I/O流)直接內(nèi)存
一個(gè)字節(jié)等于8個(gè)bit位纽疟,short 兩字節(jié) int 四字節(jié) long int 8字節(jié)
char 兩字節(jié) float 32字節(jié) double 64字節(jié)
longs/doubles、ints憾赁、shorts/chars污朽、bytes/booleans、oops(Ordinary Object Pointers)
使用句柄來(lái)訪問(wèn)的最大好處就是reference中存儲(chǔ)的是穩(wěn)定的句柄地址龙考,在對(duì)象被移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針蟆肆,而reference本身不需要修改。使用直接指針訪問(wèn)方式的最大好處就是速度更快晦款,它節(jié)省了一次指針定位的時(shí)間開銷炎功,由于對(duì)象的訪問(wèn)在Java中非常頻繁,因此這類開銷積少成多后也是一項(xiàng)非郴航Γ可觀的執(zhí)行成本蛇损。
坑:常量池中默認(rèn)有java字符這個(gè)實(shí)例所有它出現(xiàn)過(guò)。坛怪。淤齐。fffffuck
因?yàn)椤癹ava”這個(gè)字符串在執(zhí)行StringBuilder.toString()之前已經(jīng)出現(xiàn)過(guò)
很多教科書判斷對(duì)象是否存活的算法是這樣的:給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí)袜匿,計(jì)數(shù)器值就加1更啄;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1居灯;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的祭务。
這個(gè)算法的基本思路就是通過(guò)一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索怪嫌,搜索所走過(guò)的路徑稱為引用鏈(Reference Chain)义锥,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(用圖論的話來(lái)說(shuō),就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí)岩灭,則證明此對(duì)象是不可用的拌倍。如圖3-1所示,對(duì)象object 5川背、object 6贰拿、object 7雖然互相有關(guān)聯(lián)蛤袒,但是它們到GC Roots是不可達(dá)的,所以它們將會(huì)被判定為是可回收的對(duì)象膨更。
在Java語(yǔ)言中妙真,可作為GC Roots的對(duì)象包括下面幾種:
虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象。
方法區(qū)中類靜態(tài)屬性引用的對(duì)象荚守。
方法區(qū)中常量引用的對(duì)象珍德。
本地方法棧中JNI(即一般說(shuō)的Native方法)引用的對(duì)象。
Java對(duì)引用的概念進(jìn)行了擴(kuò)充矗漾,將引用分為強(qiáng)引用(Strong Reference)锈候、軟引用(Soft Reference)、弱引用(Weak Reference)敞贡、虛引用(Phantom Reference)4種泵琳,這4種引用強(qiáng)度依次逐漸減弱。
強(qiáng)引用就是指在程序代碼之中普遍存在的誊役,類似“Object obj = new Object()”這類的引用获列,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象蛔垢。
軟引用是用來(lái)描述一些還有用但并非必需的對(duì)象击孩。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前鹏漆,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收巩梢。如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常艺玲。在JDK 1.2之后括蝠,提供了SoftReference類來(lái)實(shí)現(xiàn)軟引用。
弱引用也是用來(lái)描述非必需對(duì)象的板驳,但是它的強(qiáng)度比軟引用更弱一些又跛,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時(shí)若治,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象感混。在JDK 1.2之后端幼,提供了WeakReference類來(lái)實(shí)現(xiàn)弱引用。
虛引用也稱為幽靈引用或者幻影引用弧满,它是最弱的一種引用關(guān)系婆跑。一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響庭呜,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象實(shí)例滑进。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知犀忱。在JDK 1.2之后,提供了PhantomReference類來(lái)實(shí)現(xiàn)虛引用扶关。
待解讀
在大量使用反射阴汇、動(dòng)態(tài)代理、CGLib等ByteCode框架节槐、動(dòng)態(tài)生成JSP以及OSGi這類頻繁自定義ClassLoader的場(chǎng)景都需要虛擬機(jī)具備類卸載的功能搀庶,以保證永久代不會(huì)溢出。
[插圖]
就是把內(nèi)存分為新生代和老年代铜异,并且用不同的算法進(jìn)行內(nèi)存回收哥倔。
分代收集算法
區(qū)別在于是否同時(shí)執(zhí)行,并行是同時(shí)執(zhí)行揍庄,并發(fā)是交替執(zhí)行
并發(fā)和并行
并行(Parallel):指多條垃圾收集線程并行工作咆蒿,但此時(shí)用戶線程仍然處于等待狀態(tài)。 并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的蚂子,可能會(huì)交替執(zhí)行)沃测,用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個(gè)CPU上缆镣。
Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同芽突,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughput)董瞻。所謂吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值寞蚌,即吞吐量 = 運(yùn)行用戶代碼時(shí)間 /(運(yùn)行用戶代碼時(shí)間 +垃圾收集時(shí)間),虛擬機(jī)總共運(yùn)行了100分鐘钠糊,其中垃圾收集花掉1分鐘挟秤,那吞吐量就是99%
使用G1收集器時(shí),Java堆的內(nèi)存布局就與其他收集器有很大差別抄伍,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)艘刚,雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了截珍,它們都是一部分Region(不需要連續(xù))的集合
G1將內(nèi)存區(qū)域劃分為多個(gè)大小相等的區(qū)域攀甚,這些區(qū)域內(nèi)部分代。G1通過(guò)跟蹤各個(gè)內(nèi)存區(qū)域岗喉,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表秋度,根據(jù)優(yōu)先級(jí)進(jìn)行回收。從而實(shí)現(xiàn)預(yù)測(cè)停頓時(shí)間钱床。
G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大屑运埂(回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間事期,優(yōu)先回收價(jià)值最大的Region(這也就是Garbage-First名稱的來(lái)由)滥壕。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)G1把內(nèi)存“化整為零”的思路,理解起來(lái)似乎很容易兽泣,但其中的實(shí)現(xiàn)細(xì)節(jié)卻遠(yuǎn)遠(yuǎn)沒(méi)有想象中那樣簡(jiǎn)單绎橘,否則也不會(huì)從2004年Sun實(shí)驗(yàn)室發(fā)表第一篇G1的論文開始直到
如果不計(jì)算維護(hù)Remembered Set的操作,G1收集器的運(yùn)作大致可劃分為以下幾個(gè)步驟:[插圖]初始標(biāo)記(Initial Marking)[插圖]并發(fā)標(biāo)記(Concurrent Marking)[插圖]最終標(biāo)記(Final Marking)[插圖]篩選回收(Live Data Counting and Evacuation)
對(duì)象在Survivor區(qū)中每“熬過(guò)”一次Minor GC撞叨,年齡就增加1歲金踪,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲),就將會(huì)被晉升到老年代中牵敷。
虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代胡岔,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代枷餐,無(wú)須等到MaxTenuringThreshold中要求的年齡靶瘸。
第三部分 虛擬機(jī)執(zhí)行子系統(tǒng)
只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表
無(wú)符號(hào)數(shù)屬于基本的數(shù)據(jù)類型,以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ù),無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字孕讳、索引引用匠楚、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。
[插圖]
表6-6 常量池中的14種常量項(xiàng)的結(jié)構(gòu)總表[插圖]
方法里的Java代碼厂财,經(jīng)過(guò)編譯器編譯成字節(jié)碼指令后芋簿,存放在方法屬性表集合中一個(gè)名為“Code”的屬性里面
類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止璃饱,它的整個(gè)生命周期包括:加載(Loading)与斤、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)荚恶、解析(Resolution)撩穿、初始化(Initialization)、使用(Using)和卸載(Unloading)7個(gè)階段谒撼。其中驗(yàn)證冗锁、準(zhǔn)備、解析3個(gè)部分統(tǒng)稱為連接(Linking)嗤栓,這7個(gè)階段的發(fā)生順序如圖7-1所示。[插圖]圖7-1 類的生命周期
常量在編譯階段會(huì)存入調(diào)用類的常量池中
接口與類真正有所區(qū)別的是前面講述的5種“有且僅有”需要開始初始化場(chǎng)景中的第3種:當(dāng)一個(gè)類在初始化時(shí),要求其父類全部都已經(jīng)初始化過(guò)了茉帅,但是一個(gè)接口在初始化時(shí)叨叙,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時(shí)候(如引用接口中定義的常量)才會(huì)初始化堪澎。
在加載階段擂错,虛擬機(jī)需要完成以下3件事情:1)通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流。2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)樱蛤。3)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象钮呀,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口。
[插圖]符號(hào)引用(Symbolic References):符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo)昨凡,符號(hào)可以是任何形式的字面量爽醋,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。
直接引用(Direct References):直接引用可以是直接指向目標(biāo)的指針便脊、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄蚂四。
<clinit>()方法與類的構(gòu)造函數(shù)(或者說(shuō)實(shí)例構(gòu)造器<init>()方法)不同,它不需要顯式地調(diào)用父類構(gòu)造器哪痰,虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前遂赠,父類的<clinit>()方法已經(jīng)執(zhí)行完畢。因此在虛擬機(jī)中第一個(gè)被執(zhí)行的<clinit>()方法的類肯定是java.lang.Object晌杰。
對(duì)于任意一個(gè)類跷睦,都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類加載器肋演,都擁有一個(gè)獨(dú)立的類名稱空間抑诸。這句話可以表達(dá)得更通俗一些:比較兩個(gè)類是否“相等”,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義惋啃,否則哼鬓,即使這兩個(gè)類來(lái)源于同一個(gè)Class文件,被同一個(gè)虛擬機(jī)加載边灭,只要加載它們的類加載器不同异希,那這兩個(gè)類就必定不相等。
加載lib目錄下的核心類庫(kù)绒瘦,由c++編寫称簿,是虛擬機(jī)的一部分。
啟動(dòng)類加載器
用來(lái)加載擴(kuò)展庫(kù)惰帽,開發(fā)者可以直接使用
擴(kuò)展類加載器
加載我們自己編寫的java類憨降,是系統(tǒng)默認(rèn)的類加載器
應(yīng)用程序類加載器
雙親委派模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器该酗。這里類加載器之間的父子關(guān)系一般不會(huì)以繼承(Inheritance)的關(guān)系來(lái)實(shí)現(xiàn)授药,而是都使用組合(Composition)關(guān)系來(lái)復(fù)用父加載器的代碼士嚎。
棧幀(Stack Frame)是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧(Virtual Machine Stack)[插圖]的棧元素悔叽。棧幀存儲(chǔ)了方法的局部變量表莱衩、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址等信息娇澎。每一個(gè)方法從調(diào)用開始至執(zhí)行完成的過(guò)程笨蚁,都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里面從入棧到出棧的過(guò)程。
局部變量表(Local Variable Table)是一組變量值存儲(chǔ)空間趟庄,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量括细。在Java程序編譯為Class文件時(shí),就在方法的Code屬性的max_locals數(shù)據(jù)項(xiàng)中確定了該方法所需要分配的局部變量表的最大容量戚啥。
棧幀重疊的意義是什么奋单?
操作數(shù)棧中元素的數(shù)據(jù)類型必須與字節(jié)碼指令的序列嚴(yán)格匹配,在編譯程序代碼的時(shí)候虑鼎,編譯器要嚴(yán)格保證這一點(diǎn)辱匿,在類校驗(yàn)階段的數(shù)據(jù)流分析中還要再次驗(yàn)證這一點(diǎn)。再以上面的iadd指令為例炫彩,這個(gè)指令用于整型數(shù)加法匾七,它在執(zhí)行時(shí),最接近棧頂?shù)膬蓚€(gè)元素的數(shù)據(jù)類型必須為int型江兢,不能出現(xiàn)一個(gè)long和一個(gè)float使用iadd命令相加的情況昨忆。
另外,在概念模型中杉允,兩個(gè)棧幀作為虛擬機(jī)棧的元素邑贴,是完全相互獨(dú)立的。但在大多虛擬機(jī)的實(shí)現(xiàn)里都會(huì)做一些優(yōu)化處理叔磷,令兩個(gè)棧幀出現(xiàn)一部分重疊拢驾。讓下面棧幀的部分操作數(shù)棧與上面棧幀的部分局
區(qū)別是靜態(tài)類型的變化僅僅在使用時(shí)發(fā)生,變量本身的靜態(tài)類型不會(huì)被改變改基,并且最終的靜態(tài)類型是在編譯期可知的繁疤;而實(shí)際類型變化的結(jié)果在運(yùn)行期才可確定,編譯器在編譯程序的時(shí)候并不知道一個(gè)對(duì)象的實(shí)際類型是什么
char->int->long->float->double
動(dòng)態(tài)分派的過(guò)程秕狰,它和多態(tài)性的另外一個(gè)重要體現(xiàn)[插圖]——重寫(Override)有著很密切的關(guān)聯(lián)稠腊。
也就是編譯階段
Java語(yǔ)言的靜態(tài)分派屬于多分派類型。
也就是運(yùn)行階段
Java語(yǔ)言的動(dòng)態(tài)分派屬于單分派類型鸣哀。
本章中架忌,我們分析了虛擬機(jī)在執(zhí)行代碼時(shí),如何找到正確的方法我衬、如何執(zhí)行方法內(nèi)的字節(jié)碼叹放,以及執(zhí)行代碼時(shí)涉及的內(nèi)存結(jié)構(gòu)饰恕。在第6、7许昨、8三章中懂盐,我們針對(duì)Java程序是如何存儲(chǔ)的、如何載入(創(chuàng)建)的糕档,以及如何執(zhí)行的問(wèn)題把相關(guān)知識(shí)進(jìn)行了講解,第9章我們將一起看看這些理論知識(shí)在具體開發(fā)之中的經(jīng)典應(yīng)用拌喉。
它為傳入接口中的每一個(gè)方法速那,以及從java.lang. Object中繼承來(lái)的equals()、hashCode()尿背、toString()方法都生成了對(duì)應(yīng)的實(shí)現(xiàn)端仰,并且統(tǒng)一調(diào)用了InvocationHandler對(duì)象的invoke()方法(代碼中的“this.h”就是父類Proxy中保存的InvocationHandler實(shí)例變量)來(lái)實(shí)現(xiàn)這些方法的內(nèi)容,
動(dòng)態(tài)代理的原理
各個(gè)方法的區(qū)別不過(guò)是傳入的參數(shù)和Method對(duì)象有所不同而已田藐,所以無(wú)論調(diào)用動(dòng)態(tài)代理的哪一個(gè)方法荔烧,實(shí)際上都是在執(zhí)行InvocationHandler.invoke()中的代理邏輯。