摘要:直接對(duì)棧的操作只有兩個(gè),就是對(duì)棧幀的壓棧和出棧。中將永久代移除,同時(shí)增加元數(shù)據(jù)區(qū)。在中,本地方法棧和虛擬機(jī)棧是在同一塊兒區(qū)域,這完全取決于技術(shù)實(shí)現(xiàn)的決定,并未在規(guī)范中強(qiáng)制。
原文:https://github.com/linsheng97...
描述一下 JVM 的內(nèi)存區(qū)域程序計(jì)數(shù)?(PC,Program Counter Register)。在 JVM 規(guī)范中,每個(gè)線程都有它自己的程序計(jì)數(shù)?,并且任何時(shí)間一個(gè)線程都只有一個(gè)方法在執(zhí)行,也就是所謂的當(dāng)前方法。程序計(jì)數(shù)?會(huì)存儲(chǔ)當(dāng)前線程正在執(zhí)行的 Java 方法的 JVM 指令地址;或者,如果是在執(zhí)行本地方法,則是未指定值(undefined)。
Java 虛擬機(jī)棧(Java Virtual Machine Stack),早期也叫 Java 棧。每個(gè)線程在創(chuàng)建時(shí)都會(huì)創(chuàng)建一個(gè)虛擬機(jī)棧,其內(nèi)部保存一個(gè)個(gè)的棧幀(Stack Frame),對(duì)應(yīng)著一次次的 Java 方法調(diào)用。前面談程序計(jì)數(shù)?時(shí),提到了當(dāng)前方法;同理,在一個(gè)時(shí)間點(diǎn),對(duì)應(yīng)的只會(huì)有一個(gè)活動(dòng)的棧幀,通常叫作當(dāng)前幀,方法所在的類叫作當(dāng)前類。如果在該方法中調(diào)用了其他方法,對(duì)應(yīng)的新的棧幀會(huì)被創(chuàng)建出來(lái),成為新的當(dāng)前幀,一直到它返回結(jié)果或者執(zhí)行結(jié)束。JVM 直接對(duì) Java 棧的操作只有兩個(gè),就是對(duì)棧幀的壓棧和出棧。棧幀中存儲(chǔ)著局部變量表、操作數(shù)(operand)棧、動(dòng)態(tài)鏈接、方法正常退出或者異常退出的定義等。
堆(Heap),它是 Java 內(nèi)存管理的核心區(qū)域,用來(lái)放置 Java 對(duì)象實(shí)例,幾乎所有創(chuàng)建的Java 對(duì)象實(shí)例都是被直接分配在堆上。堆被所有的線程共享,在虛擬機(jī)啟動(dòng)時(shí),我們指定的“Xmx”之類參數(shù)就是用來(lái)指定最大堆空間等指標(biāo)。理所當(dāng)然,堆也是垃圾收集?重點(diǎn)照顧的區(qū)域,所以堆內(nèi)空間還會(huì)被不同的垃圾收集?進(jìn)行進(jìn)一步的細(xì)分,最有名的就是新生代、老年代的劃分。
方法區(qū)(Method Area)。這也是所有線程共享的一塊內(nèi)存區(qū)域,用于存儲(chǔ)所謂的元(Meta)數(shù)據(jù),例如類結(jié)構(gòu)信息,以及對(duì)應(yīng)的運(yùn)行時(shí)常量池、字段、方法代碼等。由于早期的 Hotspot JVM 實(shí)現(xiàn),很多人習(xí)慣于將方法區(qū)稱為永久代(Permanent Generation)。Oracle JDK 8 中將永久代移除,同時(shí)增加了元數(shù)據(jù)區(qū)(Metaspace)。
運(yùn)行時(shí)常量池(Run-Time Constant Pool),這是方法區(qū)的一部分。如果仔細(xì)分析過(guò)反編譯的類文件結(jié)構(gòu),你能看到版本號(hào)、字段、方法、超類、接口等各種信息,還有一項(xiàng)信息就是常量池。Java 的常量池可以存放各種常量信息,不管是編譯期生成的各種字面量,還是需要在運(yùn)行時(shí)決定的符號(hào)引用,所以它比一般語(yǔ)言的符號(hào)表存儲(chǔ)的信息更加寬泛。
本地方法棧(Native Method Stack)。它和 Java 虛擬機(jī)棧是非常相似的,支持對(duì)本地方法的調(diào)用,也是每個(gè)線程都會(huì)創(chuàng)建一個(gè)。在 Oracle Hotspot JVM 中,本地方法棧和 Java 虛擬機(jī)棧是在同一塊兒區(qū)域,這完全取決于技術(shù)實(shí)現(xiàn)的決定,并未在規(guī)范中強(qiáng)制。
造成OOM的原因有哪幾種?堆內(nèi)存不足是最常見(jiàn)的 OOM 原因之一,拋出的錯(cuò)誤信息是“java.lang.OutOfMemoryError:Java heap space”,原因可能千奇百怪,例如,可能存在內(nèi)存泄漏問(wèn)題;也很有可能就是堆的大小不合理,比如我們要處理比較可觀的數(shù)據(jù)量,但是沒(méi)有顯式指定 JVM 堆大小或者指定數(shù)值偏小;或者出現(xiàn) JVM 處理引用不及時(shí),導(dǎo)致堆積起來(lái),內(nèi)存無(wú)法釋放等。
虛擬機(jī)棧和本地方法棧,這里要稍微復(fù)雜一點(diǎn)。如果我們寫(xiě)一段程序不斷的進(jìn)行遞歸調(diào)用,而且沒(méi)有退出條件,就會(huì)導(dǎo)致不斷地進(jìn)行壓棧。類似這種情況,JVM 實(shí)際會(huì)拋出StackOverFlowError;當(dāng)然,如果 JVM 試圖去擴(kuò)展棧空間的的時(shí)候失敗,則會(huì)拋出OutOfMemoryError。
對(duì)于老版本的 Oracle JDK,因?yàn)橛谰么拇笮∈怯邢薜模⑶?JVM 對(duì)永久代垃圾回收(如,常量池回收、卸載不再需要的類型)非常不積極,所以當(dāng)我們不斷添加新類型的時(shí)候,永久代出現(xiàn)OutOfMemoryError 也非常多見(jiàn),尤其是在運(yùn)行時(shí)存在大量動(dòng)態(tài)類型生成的場(chǎng)合;類似 Intern 字符串緩存占用太多空間,也會(huì)導(dǎo)致 OOM 問(wèn)題。對(duì)應(yīng)的異常信息,會(huì)標(biāo)記出來(lái)和永久代相關(guān):“java.lang.OutOfMemoryError: PermGenspace
GC 算法復(fù)制(Copying)算法,我前面講到的新生代 GC,基本都是基于復(fù)制算法,將活著的對(duì)象復(fù)制到 to 區(qū)域,拷貝過(guò)程中將對(duì)象順序放置,就可以避免內(nèi)存碎片化。這么做的代價(jià)是,既然要進(jìn)行復(fù)制,既要提前預(yù)留內(nèi)存空間,有一定的浪費(fèi);另外,對(duì)于 G1 這種分拆成為大量 region 的 GC,復(fù)制而不是移動(dòng),意味著 GC 需要維護(hù) region 之間對(duì)象引用關(guān)系,這個(gè)開(kāi)銷也不小,不管是內(nèi)存占用或者時(shí)間開(kāi)銷。
標(biāo)記 - 清除(Mark-Sweep)算法,首先進(jìn)行標(biāo)記工作,標(biāo)識(shí)出所有要回收的對(duì)象,然后進(jìn)行清除。這么做除了標(biāo)記、清除過(guò)程效率有限,另外就是不可避免的出現(xiàn)碎片化問(wèn)題,這就導(dǎo)致其不適合特別大的堆;否則,一旦出現(xiàn) Full GC,暫停時(shí)間可能根本無(wú)法接受。
標(biāo)記 - 整理(Mark-Compact),類似于標(biāo)記 - 清除,但為避免內(nèi)存碎片化,它會(huì)在清理過(guò)程中將對(duì)象移動(dòng),以確保移動(dòng)后的對(duì)象占用連續(xù)的內(nèi)存空間。
G1 垃圾回收器采用的是什么垃圾回收算法?從 GC 算法的角度,G1 選擇的是復(fù)合算法,可以簡(jiǎn)化理解為:
在新生代,G1 采用的仍然是并行的復(fù)制算法,所以同樣會(huì)發(fā)生 Stop-The-World 的暫停。
在老年代,大部分情況下都是并發(fā)標(biāo)記,而整理(Compact)則是和新生代 GC 時(shí)捎帶進(jìn)行,并且不是整體性的整理,而是增量進(jìn)行的。
GC 調(diào)優(yōu)思路從性能的角度看,通常關(guān)注三個(gè)方面,內(nèi)存占用(footprint)、延時(shí)(latency)和吞吐量(throughput),大多數(shù)情況下調(diào)優(yōu)會(huì)側(cè)重于其中一個(gè)或者兩個(gè)方面的目標(biāo),很少有情況可以兼顧三個(gè)不同的角度。當(dāng)然,除了上面通常的三個(gè)方面,也可能需要考慮其他 GC 相關(guān)的場(chǎng)景,例如,OOM 也可能與不合理的 GC 相關(guān)參數(shù)有關(guān);或者,應(yīng)用啟動(dòng)速度方面的需求,GC 也會(huì)是個(gè)考慮的方面。
基本的調(diào)優(yōu)思路可以總結(jié)為:
理解應(yīng)用需求和問(wèn)題,確定調(diào)優(yōu)目標(biāo)。假設(shè),我們開(kāi)發(fā)了一個(gè)應(yīng)用服務(wù),但發(fā)現(xiàn)偶爾會(huì)出現(xiàn)性能抖動(dòng),出現(xiàn)較長(zhǎng)的服務(wù)停頓。評(píng)估用戶可接受的響應(yīng)時(shí)間和業(yè)務(wù)量,將目標(biāo)簡(jiǎn)化為,希望 GC 暫停盡量控制在 200ms 以內(nèi),并且保證一定標(biāo)準(zhǔn)的吞吐量。
掌握 JVM 和 GC 的狀態(tài),定位具體的問(wèn)題,確定真的有 GC 調(diào)優(yōu)的必要。具體有很多方法,比如,通過(guò) jstat 等工具查看 GC 等相關(guān)狀態(tài),可以開(kāi)啟 GC 日志,或者是利用操作系統(tǒng)提供的診斷工具等。例如,通過(guò)追蹤 GC 日志,就可以查找是不是 GC 在特定時(shí)間發(fā)生了長(zhǎng)時(shí)間的暫停,進(jìn)而導(dǎo)致了應(yīng)用響應(yīng)不及時(shí)。
選擇的 GC 類型是否符合我們的應(yīng)用特征,如果是,具體問(wèn)題表現(xiàn)在哪里,是 Minor GC 過(guò)長(zhǎng),還是 Mixed GC 等出現(xiàn)異常停頓情況;如果不是,考慮切換到什么類型,如 CMS 和 G1 都是更側(cè)重于低延遲的 GC 選項(xiàng)。
通過(guò)分析確定具體調(diào)整的參數(shù)或者軟硬件配置。驗(yàn)證是否達(dá)到調(diào)優(yōu)目標(biāo),如果達(dá)到目標(biāo),即可以考慮結(jié)束調(diào)優(yōu);否則,重復(fù)完成分析、調(diào)整、驗(yàn)證這
個(gè)過(guò)程。
新對(duì)象預(yù)留在年輕代
通過(guò)設(shè)置一個(gè)較大的年輕代預(yù)留新對(duì)象,設(shè)置合理的 Survivor 區(qū)并且提供 Survivor 區(qū)的使用率,可以將年輕對(duì)象保存在年輕代。
大對(duì)象進(jìn)入年老代
使用參數(shù)-XX:PetenureSizeThreshold 設(shè)置大對(duì)象直接進(jìn)入年老代的閾值
設(shè)置對(duì)象進(jìn)入年老代的年齡
這個(gè)閾值的最大值可以通過(guò)參數(shù)-XX:MaxTenuringThreshold 來(lái)設(shè)置,默認(rèn)值是 15
穩(wěn)定的 Java 堆
獲得一個(gè)穩(wěn)定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 一樣。
增大吞吐量提升系統(tǒng)性能
–Xmx380m –Xms3800m:設(shè)置 Java 堆的最大值和初始值。一般情況下,為了避免堆內(nèi)存的頻繁震蕩,導(dǎo)致系統(tǒng)性能下降,我們的做法是設(shè)置最大堆等于最小堆。假設(shè)這里把最小堆減少為最大堆的一半,即 1900m,那么 JVM 會(huì)盡可能在 1900MB 堆空間中運(yùn)行,如果這樣,發(fā)生 GC 的可能性就會(huì)比較高;
-Xss128k:減少線程棧的大小,這樣可以使剩余的系統(tǒng)內(nèi)存支持更多的線程;
-Xmn2g:設(shè)置年輕代區(qū)域大小為 2GB;
–XX:+UseParallelGC:年輕代使用并行垃圾回收收集器。這是一個(gè)關(guān)注吞吐量的收集器,可以盡可能地減少 GC 時(shí)間。
–XX:ParallelGC-Threads:設(shè)置用于垃圾回收的線程數(shù),通常情況下,可以設(shè)置和 CPU 數(shù)量相等。但在 CPU 數(shù)量比較多的情況下,設(shè)置相對(duì)較小的數(shù)值也是合理的;
–XX:+UseParallelOldGC:設(shè)置年老代使用并行回收收集器。
嘗試使用大的內(nèi)存分頁(yè)
–XX:+LargePageSizeInBytes:設(shè)置大頁(yè)的大小。
內(nèi)存分頁(yè) (Paging) 是在使用 MMU 的基礎(chǔ)上,提出的一種內(nèi)存管理機(jī)制。它將虛擬地址和物理地址按固定大小(4K)分割成頁(yè) (page) 和頁(yè)幀 (page frame),并保證頁(yè)與頁(yè)幀的大小相同。這種機(jī)制,從數(shù)據(jù)結(jié)構(gòu)上,保證了訪問(wèn)內(nèi)存的高效,并使 OS 能支持非連續(xù)性的內(nèi)存分配。
使用非占有的垃圾回收器
為降低應(yīng)用軟件的垃圾回收時(shí)的停頓,首先考慮的是使用關(guān)注系統(tǒng)停頓的 CMS 回收器,其次,為了減少 Full GC 次數(shù),應(yīng)盡可能將對(duì)象預(yù)留在年輕代。
system.gc() 的作用是什么?gc()函數(shù)的作用只是提醒虛擬機(jī):程序員希望進(jìn)行一次垃圾回收。但是它不能保證垃圾回收一定會(huì)進(jìn)行,而且具體什么時(shí)候進(jìn)行是取決于具體的虛擬機(jī)的,不同的虛擬機(jī)有不同的對(duì)策。
Parallel GC、CMS GC、ZGC、Azul Pauseless GC最主要的不同是?背后的原理也請(qǐng)簡(jiǎn)單描述下?Parallel GC的Young區(qū)采用的是Mark-Copy算法,Old區(qū)采用的是Mark-Sweep-Compact來(lái)實(shí)現(xiàn),Parallel執(zhí)行,所以決定了Parallel GC在執(zhí)行YGC、FGC時(shí)都會(huì)Stop-The-World,但完成GC的速度也會(huì)比較快。
CMS GC的Young區(qū)采用的也是Mark-Copy,Old區(qū)采用的是Concurrent Mark-Sweep,所以決定了CMS GC在對(duì)old區(qū)回收時(shí)造成的STW時(shí)間會(huì)更短,避免對(duì)應(yīng)用產(chǎn)生太大的時(shí)延影響。
G1 GC采用了Garbage First算法,比較復(fù)雜,實(shí)現(xiàn)的好呢,理論上是會(huì)比CMS GC可以更高效,同時(shí)對(duì)應(yīng)用的影響也很小。
ZGC、Azul Pauseless GC采用的算法很不一樣,尤其是Pauseless GC,其中的很重要的一個(gè)技巧是通過(guò)增加Read Barrier來(lái)更好的識(shí)別對(duì)GC而言最關(guān)鍵的references變化的情況。
當(dāng)young gen中的eden區(qū)分配滿的時(shí)候觸發(fā)young gc,當(dāng)年老代內(nèi)存不足時(shí),將執(zhí)行Major GC,也叫 Full GC。
gc()函數(shù)的作用只是提醒虛擬機(jī):程序員希望進(jìn)行一次垃圾回收。但是它不能保證垃圾回收一定會(huì)進(jìn)行,而且具體什么時(shí)候進(jìn)行是取決于具體的虛擬機(jī)的,不同的虛擬機(jī)有不同的對(duì)策。
強(qiáng)引用、軟引用、弱引用、幻象引用有什么區(qū)別?具體使用場(chǎng)景是什么?不同的引用類型,主要體現(xiàn)的是對(duì)象不同的可達(dá)性(reachable)狀態(tài)和對(duì)垃圾收集的影響。
所謂強(qiáng)引用("Strong" Reference),就是我們最常見(jiàn)的普通對(duì)象引用,只要還有強(qiáng)引用指向一個(gè)對(duì)象,就能表明對(duì)象還“活著”,垃圾收集器不會(huì)碰這種對(duì)象。對(duì)于一個(gè)普通的對(duì)象,如果沒(méi)有其他的引用關(guān)系,只要超過(guò)了引用的作用域或者顯式地將相應(yīng)(強(qiáng))引用賦值為 null,就是可以被垃圾收集的了,當(dāng)然具體回收時(shí)機(jī)還是要看垃圾收集策略。
軟引用(SoftReference),是一種相對(duì)強(qiáng)引用弱化一些的引用,可以讓對(duì)象豁免一些垃圾收集,只有當(dāng) JVM 認(rèn)為內(nèi)存不足時(shí),才會(huì)去試圖回收軟引用指向的對(duì)象。JVM 會(huì)確保在拋出OutOfMemoryError 之前,清理軟引用指向的對(duì)象。軟引用通常用來(lái)實(shí)現(xiàn)內(nèi)存敏感的緩存,如果還有空閑內(nèi)存,就可以暫時(shí)保留緩存,當(dāng)內(nèi)存不足時(shí)清理掉,這樣就保證了使用緩存的同時(shí),不會(huì)耗盡內(nèi)存。
SoftReference 在“弱引用WeakReference”中屬于最強(qiáng)的引用。SoftReference 所指向的對(duì)象,當(dāng)沒(méi)有強(qiáng)引用指向它時(shí),會(huì)在內(nèi)存中停留一段的時(shí)間,垃圾回收器會(huì)根據(jù) JVM 內(nèi)存的使用情況(內(nèi)存的緊缺程度)以及 SoftReference 的 get() 方法的調(diào)用情況來(lái)決定是否對(duì)其進(jìn)行回收。
對(duì)于幻象引用(PhantomReference ),有時(shí)候也翻譯成虛引用,你不能通過(guò)它訪問(wèn)對(duì)象。幻象引用僅僅是提供了一種確保對(duì)象被 finalize 以后,做某些事情的機(jī)制,比如,通常用來(lái)做所謂的 Post-Mortem 清理機(jī)制,如 Java 平臺(tái)自身 Cleaner 機(jī)制等,也有人利用幻象引用監(jiān)控對(duì)象的創(chuàng)建和銷毀。
Object counter = new Object(); ReferenceQueue refQueue = new ReferenceQueue<>(); PhantomReferenceJVM類加載過(guò)程
一般來(lái)說(shuō),我們把 Java 的類加載過(guò)程分為三個(gè)主要步驟:加載、鏈接、初始化。
首先是加載階段(Loading),它是 Java 將字節(jié)碼數(shù)據(jù)從不同的數(shù)據(jù)源讀取到 JVM 中,并映射為 JVM 認(rèn)可的數(shù)據(jù)結(jié)構(gòu)(Class 對(duì)象),這里的數(shù)據(jù)源可能是各種各樣的形態(tài),如 jar 文件、class 文件,甚至是網(wǎng)絡(luò)數(shù)據(jù)源等;如果輸入數(shù)據(jù)不是 ClassFile 的結(jié)構(gòu),則會(huì)拋出 ClassFormatError。加載階段是用戶參與的階段,我們可以自定義類加載?,去實(shí)現(xiàn)自己的類加載過(guò)程。
第二階段是鏈接(Linking),這是核心的步驟,簡(jiǎn)單說(shuō)是把原始的類定義信息平滑地轉(zhuǎn)化入 JVM 運(yùn)行的過(guò)程中。這里可進(jìn)一步細(xì)分為三個(gè)步驟:
驗(yàn)證(Verification),這是虛擬機(jī)安全的重要保障,JVM 需要核驗(yàn)字節(jié)信息是符合 Java 虛擬機(jī)規(guī)范的,否則就被認(rèn)為是 VerifyError,這樣就防止了惡意信息或者不合規(guī)的信息危害 JVM 的運(yùn)行,驗(yàn)證階段有可能觸發(fā)更多 class 的加載。
準(zhǔn)備(Preparation),創(chuàng)建類或接口中的靜態(tài)變量,并初始化靜態(tài)變量的初始值。但這里的“初始化”和下面的顯式初始化階段是有區(qū)別的,側(cè)重點(diǎn)在于分配所需要的內(nèi)存空間,不會(huì)去執(zhí)行更進(jìn)一步的 JVM 指令。
解析(Resolution),在這一步會(huì)將常量池中的符號(hào)引用(symbolic reference)替換為直接引用。
最后是初始化階段(initialization),這一步真正去執(zhí)行類初始化的代碼邏輯,包括靜態(tài)字段賦值的動(dòng)作,以及執(zhí)行類定義中的靜態(tài)初始化塊內(nèi)的邏輯,編譯?在編譯階段就會(huì)把這部分邏輯整理好,父類型的初始化邏輯優(yōu)先于當(dāng)前類型的邏輯。
什么是雙親委派模型?簡(jiǎn)單說(shuō)就是當(dāng)類加載?(Class-Loader)試圖加載某個(gè)類型的時(shí)候,除非父加載?找不到相應(yīng)類型,否則盡量將這個(gè)任務(wù)代理給當(dāng)前加載?的父加載?去做。使用委派模型的目的是避免重復(fù)加載 Java 類型。
類加載器的類型啟動(dòng)類加載?(Bootstrap Class-Loader),加載 jre/lib 下面的 jar 文件,如 rt.jar。它是個(gè)超級(jí)公民,即使是在開(kāi)啟了 Security Manager 的時(shí)候,JDK 仍賦予了它加載的程序 AllPermission。
擴(kuò)展類加載?(Extension or Ext Class-Loader),負(fù)責(zé)加載我們放到 jre/lib/ext/ 目錄下面的 jar 包,這就是所謂的 extension 機(jī)制。該目錄也可以通過(guò)設(shè)置 “java.ext.dirs”來(lái)覆蓋。
應(yīng)用類加載?(Application or App Class-Loader),就是加載我們最熟悉的 classpath
上下文類加載器Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見(jiàn)的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫(kù)來(lái)提供,而這些 SPI 的實(shí)現(xiàn)代碼則是作為 Java 應(yīng)用所依賴的 jar 包被包含進(jìn)類路徑(CLASSPATH)里。SPI接口中的代碼經(jīng)常需要加載具體的實(shí)現(xiàn)類。那么問(wèn)題來(lái)了,SPI的接口是Java核心庫(kù)的一部分,是由啟動(dòng)類加載器(Bootstrap Classloader)來(lái)加載的;SPI的實(shí)現(xiàn)類是由系統(tǒng)類加載器(System ClassLoader)來(lái)加載的。引導(dǎo)類加載器是無(wú)法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)橐勒针p親委派模型,BootstrapClassloader無(wú)法委派AppClassLoader來(lái)加載類。而線程上下文類加載器破壞了“雙親委派模型”,可以在執(zhí)行線程中拋棄雙親委派加載鏈模式,使程序可以逆向使用類加載器。
ServiceLoader 的加載代碼:
public staticServiceLoaderload(Classservice) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
ContextClassLoader默認(rèn)存放了AppClassLoader的引用,由于它是在運(yùn)行時(shí)被放在了線程中,所以不管當(dāng)前程序處于何處(BootstrapClassLoader或是ExtClassLoader等),在任何需要的時(shí)候都可以用Thread.currentThread().getContextClassLoader()取出應(yīng)用程序類加載器來(lái)完成需要的操作。
自定義類加載器自定義類加載?,常見(jiàn)的場(chǎng)景有:
實(shí)現(xiàn)類似進(jìn)程內(nèi)隔離,類加載?實(shí)際上用作不同的命名空間,以提供類似容?、模塊化的效果。例如,兩個(gè)模塊依賴于某個(gè)類庫(kù)的不同版本,如果分別被不同的容?加載,就可以互不干擾。這個(gè)方面的集大成者是Java EE和OSGI、JPMS等框架。
應(yīng)用需要從不同的數(shù)據(jù)源獲取類定義信息,例如網(wǎng)絡(luò)數(shù)據(jù)源,而不是本地文件系統(tǒng)。
需要自己操縱字節(jié)碼,動(dòng)態(tài)修改或者生成類型
從本地路徑 load class 的例子:
public class CustomClassLoader extends ClassLoader { @Override public Class findClass(String name) throws ClassNotFoundException { byte[] b = loadClassFromFile(name); return defineClass(name, b, 0, b.length); } private byte[] loadClassFromFile(String fileName) { InputStream inputStream = getClass().getClassLoader().getResourceAsStream( fileName.replace(".", File.separatorChar) + ".class"); byte[] buffer; ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); int nextValue = 0; try { while ( (nextValue = inputStream.read()) != -1 ) { byteStream.write(nextValue); } } catch (IOException e) { e.printStackTrace(); } buffer = byteStream.toByteArray(); return buffer; } }動(dòng)態(tài)代理的原理?
反射機(jī)制是 Java 語(yǔ)言提供的一種基礎(chǔ)功能,賦予程序在運(yùn)行時(shí)自省(introspect,官方用語(yǔ))的能力。通過(guò)反射我們可以直接操作類或者對(duì)象,比如獲取某個(gè)對(duì)象的類定義,獲取類聲明的屬性和方法,調(diào)用方法或者構(gòu)造對(duì)象,甚至可以運(yùn)行時(shí)修改類定義。 動(dòng)態(tài)代理是一種方便運(yùn)行時(shí)動(dòng)態(tài)構(gòu)建代理、動(dòng)態(tài)處理代理方法調(diào)用的機(jī)制,很多場(chǎng)景都是利用類似機(jī)制做到的,比如用來(lái)包裝 RPC 調(diào)用、面向切面的編程(AOP)。 實(shí)現(xiàn)動(dòng)態(tài)代理的方式很多,比如 JDK 自身提供的動(dòng)態(tài)代理,就是主要利用了上面提到的反射機(jī)制。還有其他的實(shí)現(xiàn)方式,比如利用傳說(shuō)中更高性能的字節(jié)碼操作機(jī)制,類似 ASM、cglib(基于 ASM)、Javassist 等。
如何使用JDK動(dòng)態(tài)代理?public class MyDynamicProxy { public static void main (String[] args) { HelloImpl hello = new HelloImpl(); MyInvocationHandler handler = new MyInvocationHandler(hello); // 構(gòu)造代碼實(shí)例 Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler); // 調(diào)用代理方法 proxyHello.sayHello(); } } interface Hello { void sayHello(); } class HelloImpl implements Hello { @Override public void sayHello() { System.out.println("Hello World"); } } class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Invoking sayHello"); Object result = method.invoke(target, args); return result; } }動(dòng)態(tài)代理:JDK動(dòng)態(tài)代理和CGLIB代理的區(qū)別?
JDK動(dòng)態(tài)代理只能對(duì)實(shí)現(xiàn)了接口的類生成代理,而不能針對(duì)類,CGLIB是針對(duì)類實(shí)現(xiàn)代理,主要是對(duì)指定的類生成一個(gè)子類,覆蓋其中的方法(繼承)。
JDK Proxy 的優(yōu)勢(shì):
最小化依賴關(guān)系,減少依賴意味著簡(jiǎn)化開(kāi)發(fā)和維護(hù),JDK 本身的支持,可能比 cglib 更加可靠。
平滑進(jìn)行 JDK 版本升級(jí),而字節(jié)碼類庫(kù)通常需要進(jìn)行更新以保證在新版 Java 上能夠使用。
代碼實(shí)現(xiàn)簡(jiǎn)單。
基于類似 cglib 框架的優(yōu)勢(shì):
有的時(shí)候調(diào)用目標(biāo)可能不便實(shí)現(xiàn)額外接口,從某種角度看,限定調(diào)用者實(shí)現(xiàn)接口是有些侵入性的實(shí)踐,類似 cglib 動(dòng)態(tài)代理就沒(méi)有這種限制。
只操作我們關(guān)心的類,而不必為其他相關(guān)類增加工作量。
高性能。
Spring在選擇用JDK還是CGLiB的依據(jù)是什么?(1)當(dāng)Bean實(shí)現(xiàn)接口時(shí),Spring就會(huì)用JDK的動(dòng)態(tài)代理
(2)當(dāng)Bean沒(méi)有實(shí)現(xiàn)接口時(shí),Spring使用CGlib是實(shí)現(xiàn)
(3)可以強(qiáng)制使用CGlib(在spring配置中加入
(1)使用CGLib實(shí)現(xiàn)動(dòng)態(tài)代理,CGLib底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,比使用Java反射效率要高。唯一需要注意的是,CGLib不能對(duì)聲明為final的方法進(jìn)行代理,因?yàn)镃GLib原理是動(dòng)態(tài)生成被代理類的子類。但是JDK也在升級(jí),開(kāi)始引入很多字節(jié)碼技術(shù)來(lái)實(shí)現(xiàn)部分動(dòng)態(tài)代理的功能,所以在某些測(cè)試下不一定是CGLib更快。
Java 中操作字節(jié)碼的技術(shù)ASM、Javassist、CGLib、Byte Budy。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/73888.html
摘要:原文地址游客前言金三銀四,很多同學(xué)心里大概都準(zhǔn)備著年后找工作或者跳槽。最近有很多同學(xué)都在交流群里求大廠面試題。 最近整理了一波面試題,包括安卓JAVA方面的,目前大廠還是以安卓源碼,算法,以及數(shù)據(jù)結(jié)構(gòu)為主,有一些中小型公司也會(huì)問(wèn)到混合開(kāi)發(fā)的知識(shí),至于我為什么傾向于混合開(kāi)發(fā),我的一句話就是走上編程之路,將來(lái)你要學(xué)不僅僅是這些,豐富自己方能與世接軌,做好全棧的裝備。 原文地址:游客kutd...
摘要:到十二月份,公司開(kāi)始第二波裁員,我決定主動(dòng)拿賠償走人。加一個(gè)小插曲上面的題是餓了嗎面試問(wèn)到的。想去的公司沒(méi)有面試好,不要?dú)怵H,繼續(xù)加油準(zhǔn)備。避免打擊自信心。 回顧一下自己這段時(shí)間的經(jīng)歷,九月份的時(shí)候,公司通知了裁員,我匆匆忙忙地出去面了幾家,但最終都沒(méi)有拿到offer,我感覺(jué)今年的寒冬有點(diǎn)冷。到十二月份,公司開(kāi)始第二波裁員,我決定主動(dòng)拿賠償走人。后續(xù)的面試過(guò)程我做了一些準(zhǔn)備,基本都能走...
摘要:為程序員金三銀四精心挑選的余道面試題與答案,歡迎大家向我推薦你在面試過(guò)程中遇到的問(wèn)題我會(huì)把大家推薦的問(wèn)題添加到下面的常用面試題清單中供大家參考。 為Java程序員金三銀四精心挑選的300余道Java面試題與答案,歡迎大家向我推薦你在面試過(guò)程中遇到的問(wèn)題,我會(huì)把大家推薦的問(wèn)題添加到下面的常用面試題清單中供大家參考。 前兩天寫(xiě)的以下博客,大家比較認(rèn)可,熱度不錯(cuò),希望可以幫到準(zhǔn)備或者正在參加...
閱讀 1995·2023-04-25 16:19
閱讀 3109·2021-11-24 09:39
閱讀 834·2021-11-16 11:44
閱讀 1697·2019-08-29 12:52
閱讀 1145·2019-08-26 13:33
閱讀 1079·2019-08-26 10:26
閱讀 2207·2019-08-23 16:42
閱讀 2571·2019-08-23 14:37