柚子快報(bào)邀請(qǐng)碼778899分享:java JVM高頻面試點(diǎn)
柚子快報(bào)邀請(qǐng)碼778899分享:java JVM高頻面試點(diǎn)
文章目錄
JVM內(nèi)存模型程序計(jì)數(shù)器Java虛擬機(jī)棧本地方法棧Java堆方法區(qū)運(yùn)行時(shí)常量池
Java對(duì)象對(duì)象的創(chuàng)建如何為對(duì)象分配內(nèi)存
對(duì)象的內(nèi)存布局對(duì)象頭實(shí)例數(shù)據(jù)對(duì)齊填充
對(duì)象的訪問(wèn)定位
垃圾收集器找到垃圾引用計(jì)數(shù)法可達(dá)性分析(根搜索法)
引用概念的擴(kuò)充回收方法區(qū)垃圾收集算法分代收集理論弱分代假說(shuō)(Weak Generational Hypothesis):絕大多數(shù)對(duì)象都是朝生夕滅的。強(qiáng)分代假說(shuō)(Strong Generational Hypothesis):熬過(guò)越多次垃圾收集過(guò)程的對(duì)象就越難以消亡。新生代和老年代跨代引用假說(shuō)(Intergenerational Reference Hypothesis):存在互相引用關(guān)系的兩個(gè)對(duì)象,是應(yīng)該傾 向于同時(shí)生存或者同時(shí)消亡的。分代垃圾收集
標(biāo)記-清除算法標(biāo)記-復(fù)制算法標(biāo)記-整理算法Stop The World
經(jīng)典的垃圾收集器CMS 收集器G1 收集器
內(nèi)存分配策略對(duì)象優(yōu)先在Eden區(qū)分配大對(duì)象直接進(jìn)入老年代長(zhǎng)期存活的對(duì)象將進(jìn)入老年代動(dòng)態(tài)對(duì)象年齡判定空間分配擔(dān)保
JVM類(lèi)加載機(jī)制類(lèi)加載的時(shí)機(jī)類(lèi)加載的過(guò)程加載驗(yàn)證準(zhǔn)備解析初始化
類(lèi)加載器類(lèi)與類(lèi)加載器雙親委派模型三層類(lèi)加載器雙親委派模型的機(jī)制
JVM內(nèi)存模型
Java虛擬機(jī)在執(zhí)行java程序的過(guò)程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域
這些區(qū)域各自有各自的用途,各自有各自的特性,或稱(chēng)之為運(yùn)行時(shí)數(shù)據(jù)區(qū)域
線程共享的:方法區(qū),堆線程獨(dú)享的:程序計(jì)數(shù)器,虛擬機(jī)棧,本地方法棧
程序計(jì)數(shù)器
一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
功能:記錄線程的狀態(tài),記錄指令的行號(hào),使線程切換后能恢復(fù)到正確的位置
特點(diǎn):唯一一個(gè)不會(huì)發(fā)生OOM的區(qū)域
OOM: OutOfMemory,即內(nèi)存數(shù)據(jù)溢出
Java虛擬機(jī)棧
為線程所私有,與線程的生命周期相同
**功能:**存放棧幀,每一個(gè)方法的執(zhí)行都會(huì)有一個(gè)棧幀被創(chuàng)建,用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)連接、方法出口等信息,每一個(gè)方法被調(diào)用到執(zhí)行完畢的過(guò)程,都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程
局部變量表中存放了基本類(lèi)型的數(shù)據(jù)和引用類(lèi)型數(shù)據(jù)的引用(起始地址)
在《Java虛擬機(jī)規(guī)范》中,對(duì)這個(gè)內(nèi)存區(qū)域規(guī)定了兩類(lèi)異常狀況:
如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;如果Java虛擬機(jī)棧容量可以動(dòng)態(tài)擴(kuò)展,當(dāng)棧擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存會(huì)拋出OutOfMemoryError異常,不過(guò)現(xiàn)在使用的HotSpot虛擬機(jī)是不可以動(dòng)態(tài)擴(kuò)展的
本地方法棧
本地方法棧與虛擬機(jī)棧非常相似,區(qū)別只是后者為Java方法服務(wù),前者為虛擬機(jī)用到的本地方法服務(wù)。
本地方法棧也會(huì)在棧深度溢出或者棧擴(kuò)展失敗時(shí)分別拋出StackOverflowError和OutOfMemoryError異常
Java堆
Java堆是虛擬機(jī)所管理的內(nèi)存中最大的一塊,是被所有線程共享的區(qū)域
功能:存放對(duì)象實(shí)例以及數(shù)組
Java堆正是垃圾收集器管理的內(nèi)存區(qū)域,因?yàn)樗^垃圾回收(GC)回收的就是對(duì)象。
堆的工作就是為線程的對(duì)象分配空間,同時(shí)為了減少線程之間爭(zhēng)搶資源的情況, Java堆中可以劃分出多個(gè)線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)
方法區(qū)
方法區(qū)是一個(gè)線程共享的區(qū)域
功能:存儲(chǔ)已被虛擬機(jī)加載的類(lèi)型信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池是方法區(qū)的一部分,.class文件中除了有類(lèi)的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池表,用于存放編譯期生成的各種字面量與符號(hào)引用,這部分內(nèi)容將在類(lèi)加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中。
Java對(duì)象
對(duì)象的創(chuàng)建
首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類(lèi)的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類(lèi)是否已被加載、解析和初始化過(guò)。如果沒(méi)有,那必須先執(zhí)行相應(yīng)的類(lèi)加載過(guò)程,在類(lèi)加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存。
如何為對(duì)象分配內(nèi)存
為對(duì)象分配空間的任務(wù)實(shí)際上便等同于把一塊確定大小的內(nèi)存塊從Java堆中劃分出來(lái)。
指針碰撞 假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有被使用過(guò)的內(nèi)存都被放在一邊,空閑的內(nèi)存被放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間方向挪動(dòng)一段與對(duì)象大小相等的距離,這種分配方式稱(chēng)為“指針碰撞”(Bump The Pointer)。 空閑列表 但如果Java堆中的內(nèi)存并不是規(guī)整的,已被使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò)在一起,那就沒(méi)有辦法簡(jiǎn)單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄,這種分配方式稱(chēng)為“空閑列表”(Free List)。
選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有空間壓縮整理(Compact)的能力決定。
對(duì)象的內(nèi)存布局
對(duì)象頭
對(duì)象頭部分包括兩類(lèi)信息。
第一類(lèi)是用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時(shí)間戳等,
對(duì)象頭的另外一部分是類(lèi)型指針,即對(duì)象指向它的類(lèi)型元數(shù)據(jù)的指針。,Java虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定該對(duì)象是哪個(gè)類(lèi)的實(shí)例。
此外,如果對(duì)像是一個(gè)Java數(shù)組,那在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù),因?yàn)樘摂M機(jī)可以通過(guò)普通 Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小,但是如果數(shù)組的長(zhǎng)度是不確定的,將無(wú)法通過(guò)元數(shù)據(jù)中的信息推斷出數(shù)組的大小。
實(shí)例數(shù)據(jù)
對(duì)象真正存儲(chǔ)的有效信息,即我們?cè)诔绦虼a里面所定義的各種類(lèi)型的字段內(nèi)容,無(wú)論是從父類(lèi)繼承下來(lái)的,還是在子類(lèi)中定義的字段都必須記錄起來(lái)
對(duì)齊填充
對(duì)象的第三部分是對(duì)齊填充,這并不是必然存在的,也沒(méi)有特別的含義,它僅僅起著占位符的作用。由于HotSpot虛擬機(jī)的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,換句話說(shuō)就是任何對(duì)象的大小都必須是8字節(jié)的整數(shù)倍,對(duì)象頭部分已經(jīng)被精心設(shè)計(jì)成正好是8字節(jié)的倍數(shù)(1倍或者2倍),因此,如果對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊的話,就需要通過(guò)對(duì)齊填充來(lái)補(bǔ)全。
對(duì)象的訪問(wèn)定位
創(chuàng)建對(duì)象自然是為了后續(xù)使用該對(duì)象,我們的Java程序會(huì)通過(guò)棧上的reference數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。
由于reference類(lèi)型在《Java虛擬機(jī)規(guī)范》里面只規(guī)定了它是一個(gè)指向?qū)ο蟮囊?,并沒(méi)有定義這個(gè)引用應(yīng)該通過(guò)什么方式去定位、訪問(wèn)到堆中對(duì)象的具體位置,所以對(duì)象訪問(wèn)方式也是由虛擬機(jī)實(shí)現(xiàn)而定的。
主流的訪問(wèn)方式主要有使用句柄和直接指針兩種:
如果使用句柄訪問(wèn)的話,Java堆中將可能會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類(lèi)型數(shù)據(jù)各自具體的地址信息。如果使用直接指針訪問(wèn)的話,Java堆中對(duì)象的內(nèi)存布局就必須考慮如何放置訪問(wèn)類(lèi)型數(shù)據(jù)的相關(guān)信息,reference中存儲(chǔ)的直接就是對(duì)象地址,如果只是訪問(wèn)對(duì)象本身的話,就不需要多一次間接訪問(wèn)的開(kāi)銷(xiāo)
使用句柄的好處就是當(dāng)垃圾回收時(shí),需要移動(dòng)對(duì)象的位置,此時(shí)只需要修改句柄中指向?qū)嵗龜?shù)據(jù)的指針即可,而直接指針的訪問(wèn)方式就必須在移動(dòng)對(duì)象之后修改所有reference的值,因?yàn)橐粋€(gè)對(duì)象往往不止一個(gè)引用。
使用直接指針的好處也顯而易見(jiàn),就是快,比句柄訪問(wèn)少一次指針定位的開(kāi)銷(xiāo)
就HotSpot而言,它主要使用第二種方式進(jìn)行對(duì)象訪問(wèn),并且從整個(gè)軟件開(kāi)發(fā)的范圍來(lái)看,在各種語(yǔ)言、框架中使用句柄來(lái)訪問(wèn)的情況也十分常見(jiàn)。
垃圾收集器
垃圾收集(Garbage Collection,GC),這一板塊我們需要搞懂三個(gè)問(wèn)題:
what:回收什么when:什么時(shí)候回收how:怎么回收
垃圾收集器是回收內(nèi)存中的垃圾的,那么什么是垃圾? 作為初學(xué)者,我們只需要知道這里說(shuō)的垃圾,就是沒(méi)用的對(duì)象,雖然這么說(shuō)不太準(zhǔn)確,但對(duì)開(kāi)發(fā)者來(lái)說(shuō),知道這些就夠了,除非你要去開(kāi)發(fā)虛擬機(jī)
找到垃圾
回收垃圾的第一步,找到垃圾,即沒(méi)用的對(duì)象,那么什么樣的對(duì)象是沒(méi)用的呢,我們需要借助以下兩者算法
引用計(jì)數(shù)法
在對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加一;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減一;任何時(shí)刻計(jì)數(shù)器為零的對(duì)象就是不可能再被使用的。
優(yōu)點(diǎn):簡(jiǎn)單快速
缺點(diǎn):無(wú)法解決循環(huán)引用的問(wèn)題
可達(dá)性分析(根搜索法)
這個(gè)算法的基本思路就是通過(guò)一系列稱(chēng)為“GC Roots”的根對(duì)象作為起始節(jié)點(diǎn)集,從這些節(jié)點(diǎn)開(kāi)始,根據(jù)引用關(guān)系向下搜索過(guò)程所走過(guò)的路徑稱(chēng)為“引用鏈”(Reference Chain)如果某個(gè)對(duì)象到GC Roots間沒(méi)有任何引用鏈相連,或者用圖論的話來(lái)說(shuō)就是從GC Roots到這個(gè)對(duì)象不可達(dá)時(shí),則證明此對(duì)象是不可能再被使用的
在Java技術(shù)體系里面,固定可作為GC Roots的對(duì)象包括以下幾種:
在虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象,譬如各個(gè)線程被調(diào)用的方法堆棧中使用到的 參數(shù)、局部變量、臨時(shí)變量等。在方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象,譬如Java類(lèi)的引用類(lèi)型靜態(tài)變量。在方法區(qū)中常量引用的對(duì)象,譬如字符串常量池(String Table)里的引用。在本地方法棧中JNI(即通常所說(shuō)的Native方法)引用的對(duì)象。Java虛擬機(jī)內(nèi)部的引用,如基本數(shù)據(jù)類(lèi)型對(duì)應(yīng)的Class對(duì)象,一些常駐的異常對(duì)象(比如 NullPointExcepiton、OutOfMemoryError)等,還有系統(tǒng)類(lèi)加載器。所有被同步鎖(synchronized關(guān)鍵字)持有的對(duì)象。反映Java虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI中注冊(cè)的回調(diào)、本地代碼緩存等。除了這些固定的GC Roots集合以外,根據(jù)用戶(hù)所選用的垃圾收集器以及當(dāng)前回收的內(nèi)存區(qū)域不同,還可以有其他對(duì)象“臨時(shí)性”地加入,共同構(gòu)成完整GC Roots集合。
我們的HotSpot虛擬機(jī)采用的就是可達(dá)性分析算法
引用概念的擴(kuò)充
無(wú)論是通過(guò)引用計(jì)數(shù)算法判斷對(duì)象的引用數(shù)量,還是通過(guò)可達(dá)性分析算法判斷對(duì)象是否引用鏈可達(dá),判定對(duì)象是否存活都和“引用”離不開(kāi)關(guān)系。
在JDK 1.2版之前,Java里面的引用是很傳統(tǒng)的定義:如果reference類(lèi)型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱(chēng)該reference數(shù)據(jù)是代表某塊內(nèi)存、某個(gè)對(duì)象的引用。
這種定義并沒(méi)有什么不對(duì),只是現(xiàn)在看來(lái)有些過(guò)于狹隘了,一個(gè)對(duì)象在這種定義下只有“被引用”或者“未被引用”兩種狀態(tài),對(duì)于描述一些“食之無(wú)味,棄之可惜”的對(duì)象就顯得無(wú)能為力。譬如我們希望能描述一類(lèi)對(duì)象:當(dāng)內(nèi)存空間還足夠時(shí),能保留在內(nèi)存之中,如果內(nèi)存空間在進(jìn)行垃圾收集后仍然非常緊張,那就可以拋棄這些對(duì)象——很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場(chǎng)景。
在JDK 1.2版之后,Java對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strongly Re-ference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)4種,這4種引用強(qiáng)度依次逐漸減弱。
強(qiáng)引用是最傳統(tǒng)的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類(lèi)似“Object obj=new Object()”這種引用關(guān)系。無(wú)論任何情況下,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。軟引用是用來(lái)描述一些還有用,但非必須的對(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)來(lái)實(shí)現(xiàn)軟引用。弱引用也是用來(lái)描述那些非必須對(duì)象,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只 能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開(kāi)始工作,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只 被弱引用關(guān)聯(lián)的對(duì)象。在JDK 1.2版之后提供了WeakReference類(lèi)來(lái)實(shí)現(xiàn)弱引用。虛引用也稱(chēng)為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關(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)來(lái)實(shí)現(xiàn)虛引用
回收方法區(qū)
方法區(qū)回收囿于苛刻的判定條件,其區(qū)域垃圾收集的回收成果往往遠(yuǎn)低于此。 方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:廢棄的常量和不再使用的類(lèi)型。
回收廢棄常量與回收 Java堆中的對(duì)象非常類(lèi)似。舉個(gè)常量池中字面量回收的例子,假如一個(gè)字符串“java”曾經(jīng)進(jìn)入常量池中,但是當(dāng)前系統(tǒng)又沒(méi)有任何一個(gè)字符串對(duì)象的值是“java”,換句話說(shuō),已經(jīng)沒(méi)有任何字符串對(duì)象引用常量池中的“java”常量,且虛擬機(jī)中也沒(méi)有其他地方引用這個(gè)字面量。如果在這時(shí)發(fā)生內(nèi)存回收,而且垃圾收集器判斷確有必要的話,這個(gè)“java”常量就將會(huì)被系統(tǒng)清理出常量池。常量池中其他類(lèi)(接口)、方法、字段的符號(hào)引用也與此類(lèi)似。
判定一個(gè)常量是否“廢棄”還是相對(duì)簡(jiǎn)單,而要判定一個(gè)類(lèi)型是否屬于“不再被使用的類(lèi)”的條件就比較苛刻了。需要同時(shí)滿(mǎn)足下面三個(gè)條件:
該類(lèi)所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類(lèi)及其任何派生子類(lèi)的實(shí)例。加載該類(lèi)的類(lèi)加載器已經(jīng)被回收,這個(gè)條件除非是經(jīng)過(guò)精心設(shè)計(jì)的可替換類(lèi)加載器的場(chǎng)景,如 OSGi、JSP的重加載等,否則通常是很難達(dá)成的。該類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類(lèi)的方法。
Java虛擬機(jī)被允許對(duì)滿(mǎn)足上述三個(gè)條件的無(wú)用類(lèi)進(jìn)行回收,這里說(shuō)的僅僅是“被允許”,而并不是和對(duì)象一樣,沒(méi)有引用了就必然會(huì)回收
垃圾收集算法
分代收集理論
弱分代假說(shuō)(Weak Generational Hypothesis):絕大多數(shù)對(duì)象都是朝生夕滅的。
強(qiáng)分代假說(shuō)(Strong Generational Hypothesis):熬過(guò)越多次垃圾收集過(guò)程的對(duì)象就越難以消亡。
這兩個(gè)分代假說(shuō)共同奠定了多款常用的垃圾收集器的一致的設(shè)計(jì)原則:
收集器應(yīng)該將Java堆劃分出不同的區(qū)域,然后將回收對(duì)象依據(jù)其年齡(年齡即對(duì)象熬過(guò)垃圾收集過(guò)程的次數(shù))分配到不同的區(qū)域之中存儲(chǔ)。
顯而易見(jiàn),如果一個(gè)區(qū)域中大多數(shù)對(duì)象都是朝生夕滅,難以熬過(guò)垃圾收集過(guò)程的話,那么把它們集中放在一起,每次回收時(shí)只關(guān)注如何保留少量存活而不是去標(biāo)記那些大量將要被回收的對(duì)象,就能以較低代價(jià)回收到大量的空間;如果剩下的都是難以消亡的對(duì)象,那把它們集中放在一塊,虛擬機(jī)便可以使用較低的頻率來(lái)回收這個(gè)區(qū)域,這就同時(shí)兼顧了垃圾收集的時(shí)間開(kāi)銷(xiāo)和內(nèi)存的空間有效利用。
新生代和老年代
設(shè)計(jì)者一般至少會(huì)把Java堆劃分為新生代(Young Generation)和老年代(Old Generation)兩個(gè)區(qū)域[。
顧名思義,在新生代中,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,而每次回收后存活的少量對(duì)象,將會(huì)逐步晉升到老年代中存放。
跨代引用假說(shuō)(Intergenerational Reference Hypothesis):存在互相引用關(guān)系的兩個(gè)對(duì)象,是應(yīng)該傾 向于同時(shí)生存或者同時(shí)消亡的。
如果某個(gè)新生代對(duì)象存在跨代引用,由于老年代對(duì)象難以消亡,該引用會(huì)使得新生代對(duì)象在收集時(shí)同樣得以存活,進(jìn)而在年齡增長(zhǎng)之后晉升到老年代中,這時(shí)跨代引用也隨即被消除了。
依據(jù)這條假說(shuō),我們就不應(yīng)再為了少量的跨代引用去掃描整個(gè)老年代,也不必浪費(fèi)空間專(zhuān)門(mén)記錄每一個(gè)對(duì)象是否存在及存在哪些跨代引用,只需在新生代上建立一個(gè)全局的數(shù)據(jù)結(jié)構(gòu)(該結(jié)構(gòu)被稱(chēng)為“記憶集”,Remembered Set),這個(gè)結(jié)構(gòu)把老年代劃分成若干小塊,標(biāo)識(shí)出老年代的哪一塊內(nèi)存會(huì)存在跨代引用。此后當(dāng)發(fā)生Minor GC時(shí),只有包含了跨代引用的小塊內(nèi)存里的對(duì)象才會(huì)被加入到GC Roots進(jìn)行掃描。雖然這種方法需要在對(duì)象改變引用關(guān)系(如將自己或者某個(gè)屬性賦值)時(shí)維護(hù)記錄數(shù)據(jù)的正確性,會(huì)增加一些運(yùn)行時(shí)的開(kāi)銷(xiāo),但比起收集時(shí)掃描整個(gè)老年代來(lái)說(shuō)仍然是劃算的。
分代垃圾收集
部分收集(Partial GC):指目標(biāo)不是完整收集整個(gè)Java堆的垃圾收集,其中又分為:
新生代收集(Minor GC/Young GC):指目標(biāo)只是新生代的垃圾收集。老年代收集(Major GC/Old GC):指目標(biāo)只是老年代的垃圾收集。目前只有CMS收集器會(huì)有單獨(dú)收集老年代的行為。另外請(qǐng)注意“Major GC”這個(gè)說(shuō)法現(xiàn)在有點(diǎn)混淆,在不同資料上常有不同所指,讀者需按上下文區(qū)分到底是指老年代的收集還是整堆收集?;旌鲜占∕ixed GC):指目標(biāo)是收集整個(gè)新生代以及部分老年代的垃圾收集。目前只有G1收 集器會(huì)有這種行為。 整堆收集(Full GC):收集整個(gè)Java堆和方法區(qū)的垃圾收集。
標(biāo)記-清除算法
先標(biāo)記需要回收的對(duì)象,再統(tǒng)一回收
標(biāo)記-復(fù)制算法
將內(nèi)存劃分成相等兩塊Eden和Survivor,一塊用完了就把存活的對(duì)象復(fù)制的另一邊,以此往復(fù),但是為了避免浪費(fèi)太多空間,而且大部分對(duì)象具有朝生夕死的特性
現(xiàn)在采用Eden:Survivor=8:1:1的方式,劃分三塊,每次都將 Elden 和正在用的那塊Survivor 空間復(fù)制到另一塊空閑的Survivor,以此往復(fù)
由于無(wú)法保證每次垃圾回收后存活的對(duì)象都不超過(guò)10%,所有當(dāng)Survivor區(qū)不夠用時(shí),可以直接將多余的存活對(duì)象直接放入老年代,稱(chēng)之為老年代分配擔(dān)保機(jī)制
標(biāo)記-整理算法
回收對(duì)象后,將所有存活的對(duì)象向內(nèi)存空間的一端移動(dòng),然后直接清理邊界以外的內(nèi)存。 可以避免內(nèi)存空間過(guò)于碎片化,有利于內(nèi)存分配
Stop The World
如果采用了移動(dòng)對(duì)象的垃圾收集算法,那么在移動(dòng)對(duì)象時(shí)就必須暫停用于應(yīng)用程序,稱(chēng)之為Stop The World
經(jīng)典的垃圾收集器
CMS 收集器
CMS (Concurrent Mark Sweep),基于標(biāo)記-清除算法,
運(yùn)作過(guò)程:-初始標(biāo)記,-并發(fā)標(biāo)記,-重新標(biāo)記,-并發(fā)清除
優(yōu)點(diǎn):并發(fā)收集,低停頓
缺點(diǎn):對(duì)處理器資源敏感,無(wú)法處理浮動(dòng)垃圾,會(huì)產(chǎn)生大量空間碎片
G1 收集器
G1 (garbage first) 收集器,面向服務(wù)端,是一款全功能垃圾收集器,開(kāi)創(chuàng)基于Region的堆內(nèi)存布局。能實(shí)現(xiàn)在指定長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不超過(guò)N毫秒的功能。
運(yùn)作過(guò)程:-初始標(biāo)記,-并發(fā)標(biāo)記,-最終標(biāo)記,-篩選回收
內(nèi)存分配策略
對(duì)象優(yōu)先在Eden區(qū)分配
大多數(shù)情況下,對(duì)象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒(méi)有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次Minor GC。
大對(duì)象直接進(jìn)入老年代
大對(duì)象就是指需要大量連續(xù)內(nèi)存空間的Java對(duì)象,最典型的大對(duì)象便是那種很長(zhǎng)的字符串,或者元素?cái)?shù)量很龐大的數(shù)組,大對(duì)象對(duì)虛擬機(jī)的內(nèi)存分配來(lái)說(shuō)就是一個(gè)不折不扣的壞消息,比遇到一個(gè)大對(duì)象更壞的消息就是遇到一群“朝生夕滅”的“短命大對(duì)象”,我們寫(xiě)程序的時(shí)候應(yīng)注意避免。
在Java虛擬機(jī)中要避免大對(duì)象的原因是,在分配空間時(shí),它容易導(dǎo)致內(nèi)存明明還有不少空間時(shí)就提前觸發(fā)垃圾收集,以獲取足夠的連續(xù)空間才能安置好它們,而當(dāng)復(fù)制對(duì)象時(shí),大對(duì)象就意味著高額的內(nèi)存復(fù)制開(kāi)銷(xiāo)。
HotSpot虛擬機(jī)提供了-XX:PretenureSizeThreshold參數(shù),指定大于該設(shè)置值的對(duì)象直接在老年代分配,這樣做的目的就是避免在Eden區(qū)及兩個(gè)Survivor區(qū)之間來(lái)回復(fù)制,產(chǎn)生大量的內(nèi)存復(fù)制操作。
長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
多數(shù)收集器都采用了分代收集來(lái)管理堆內(nèi)存,那內(nèi)存回收時(shí)就必須能決策哪些存活對(duì)象應(yīng)當(dāng)放在新生代,哪些存活對(duì)象放在老年代中。
為做到這點(diǎn),虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(Age)計(jì)數(shù)器,存儲(chǔ)在對(duì)象頭中。 對(duì)象通常在Eden區(qū)里誕生,如果經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納的話,該對(duì)象會(huì)被移動(dòng)到Survivor空間中,并且將其對(duì)象年齡設(shè)為1歲。對(duì)象在Survivor區(qū)中每熬過(guò)一次Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15),就會(huì)被晉升到老年代中。對(duì)象晉升老年代的年齡閾值,可以通過(guò)參數(shù)-XX: MaxTenuringThreshold設(shè)置。
動(dòng)態(tài)對(duì)象年齡判定
HotSpot虛擬機(jī)并不是永遠(yuǎn)要求對(duì)象的年齡必須達(dá)到-XX:MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于 Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到-XX: MaxTenuringThreshold中要求的年齡。
空間分配擔(dān)保
在發(fā)生Minor GC之前,虛擬機(jī)必須先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,
如果這個(gè)條件成立,那這一次Minor GC可以確保是安全的。如果不成立,則虛擬機(jī)會(huì)先查看XX:HandlePromotionFailure參數(shù)的設(shè)置值是否允許擔(dān)保失?。℉andle Promotion Failure)
如果允許,那會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,
如果大于,將嘗試進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險(xiǎn)的;如果小于,或者-XX: HandlePromotionFailure設(shè)置不允許冒險(xiǎn),那這時(shí)就要改為進(jìn)行一次Full GC。
JVM類(lèi)加載機(jī)制
類(lèi)加載的時(shí)機(jī)
一個(gè)類(lèi)型從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,它的整個(gè)生命周期將會(huì)經(jīng)歷加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱(chēng)為連接(Linking)
加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這五個(gè)階段的順序是確定的,類(lèi)型的加載過(guò)程必須按 照這種順序按部就班地開(kāi)始,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開(kāi)始,
《Java虛擬機(jī)規(guī)范》則是嚴(yán)格規(guī)定了有且只有六種情況必須立即對(duì)類(lèi)進(jìn)行“初始化”(而加載、驗(yàn)證、準(zhǔn)備自然需要在此之前開(kāi)始)
遇到new、getstatic、putstatic或invokestatic這四條字節(jié)碼指令時(shí),如果類(lèi)型沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化階段。能夠生成這四條指令的典型Java代碼場(chǎng)景有:
使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候。讀取或設(shè)置一個(gè)類(lèi)型的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候。調(diào)用一個(gè)類(lèi)型的靜態(tài)方法的時(shí)候。 使用java.lang.reflect包的方法對(duì)類(lèi)型進(jìn)行反射調(diào)用的時(shí)候,如果類(lèi)型沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。當(dāng)初始化類(lèi)的時(shí)候,如果發(fā)現(xiàn)其父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類(lèi)的初始化。當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶(hù)需要指定一個(gè)要執(zhí)行的主類(lèi)(包含main()方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先 初始化這個(gè)主類(lèi)。當(dāng)使用JDK 7新加入的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解 析結(jié)果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類(lèi)型的方法句 柄,并且這個(gè)方法句柄對(duì)應(yīng)的類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。當(dāng)一個(gè)接口中定義了JDK 8新加入的默認(rèn)方法(被default關(guān)鍵字修飾的接口方法)時(shí),如果有 這個(gè)接口的實(shí)現(xiàn)類(lèi)發(fā)生了初始化,那該接口要在其之前被初始化。
類(lèi)加載的過(guò)程
加載
“加載”(Loading)階段是整個(gè)“類(lèi)加載”(Class Loading)過(guò)程中的一個(gè)階段
在加載階段,Java虛擬機(jī)需要完成以下三件事情:
通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取定義此類(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è)類(lèi)的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類(lèi)的各種數(shù)據(jù)的訪問(wèn)入 口。
驗(yàn)證
驗(yàn)證是連接階段的第一步,這一階段的目的是確保.class文件的字節(jié)流中包含的信息符合《Java虛擬機(jī)規(guī)范》的全部約束要求,保證這些信息被當(dāng)作代碼運(yùn)行后不會(huì)危害虛擬機(jī)自身的安全。
驗(yàn)證階段大致上會(huì)完成下面四個(gè)階段的檢驗(yàn)動(dòng)作:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證。
準(zhǔn)備
準(zhǔn)備階段是正式為類(lèi)中定義的變量(即靜態(tài)變量,被static修飾的變量)分配內(nèi)存并設(shè)置類(lèi)變量初始值的階段
這時(shí)候進(jìn)行內(nèi)存分配的僅包括類(lèi)變量,而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中。其次是這里所說(shuō)的初始值“通常情況”下是數(shù)據(jù)類(lèi)型的零值。
特殊情況
public static final int value = 123;
編譯時(shí)Javac將會(huì)為value生成ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue的設(shè)置將value賦值為123。
解析
解析階段是Java虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程
符號(hào)引用(Symbolic References):符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定是已經(jīng)加載到虛擬機(jī)內(nèi)存當(dāng)中的內(nèi)容。各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局可以各不相同,但是它們能接受的符號(hào)引用必須都是一致的,因?yàn)榉?hào)引用的字面量形式明確定義在《Java虛擬機(jī)規(guī)范》的*.class*文件格式中。直接引用(Direct References):直接引用是可以直接指向目標(biāo)的指針、相對(duì)偏移量或者是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局直接相關(guān)的,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在虛擬機(jī)的內(nèi)存中存在。
翻譯成人話就是程序運(yùn)行之前, .class里的引用指向的都是一個(gè)邏輯上的地址,因?yàn)檫@個(gè)時(shí)候我們的程序還只是一張圖紙,還沒(méi)有變成實(shí)體,當(dāng)虛擬機(jī)給常量分配空間之后,我們程序里的常量就有了實(shí)體,此時(shí),就需要讓我們的引用指向真實(shí)的地址。
解析動(dòng)作主要針對(duì)類(lèi)或接口、字段、類(lèi)方法、接口方法、方法類(lèi)型、方法句柄和調(diào)用點(diǎn)限定符這7類(lèi)符號(hào)引用進(jìn)行,分別對(duì)應(yīng)于常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info、CONSTANT_Dyna-mic_info和CONSTANT_InvokeDynamic_info8種常量類(lèi)型
初始化
類(lèi)的初始化階段是類(lèi)加載過(guò)程的最后一個(gè)步驟,進(jìn)行準(zhǔn)備階段時(shí),變量已經(jīng)賦過(guò)一次系統(tǒng)要求的初始零值,而在初始化階段,則會(huì)根據(jù)程序員通過(guò)程序編碼制定的主觀計(jì)劃去初始化類(lèi)變量和其他資源。
我們也可以從另外一種更直接的形式來(lái)表達(dá):初始化階段就是執(zhí)行類(lèi)構(gòu)造器
static {
i = 0; // 給變量賦值可以正常編譯通過(guò)
System.out.print(i); // 這句編譯器會(huì)提示“非法向前引用”
}
static int i = 1;
}
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B);
}
需要注意,其他線程雖然會(huì)被阻塞,但如果執(zhí)行<clinit>()方法的那條線程退出<clinit>()方法 后,其他線程喚醒后則不會(huì)再次進(jìn)入<clinit>()方法。同一個(gè)類(lèi)加載器下,一個(gè)類(lèi)型只會(huì)被初始化一 次。
類(lèi)加載器
Java虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)有意把類(lèi)加載階段中的“通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取描述該類(lèi)的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓?xiě)?yīng)用程序自己決定如何去獲取所需的類(lèi)。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼被稱(chēng)為“類(lèi)加載器”(Class Loader)。
類(lèi)與類(lèi)加載器
類(lèi)加載器雖然只用于實(shí)現(xiàn)類(lèi)的加載動(dòng)作,但它在Java程序中起到的作用卻遠(yuǎn)超類(lèi)加載階段。對(duì)于任意一個(gè)類(lèi),都必須由加載它的類(lèi)加載器和這個(gè)類(lèi)本身一起共同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類(lèi)加載器,都擁有一個(gè)獨(dú)立的類(lèi)名稱(chēng)空間。
這句話可以表達(dá)得更通俗一些:比較兩個(gè)類(lèi)是否“相等”,只有在這兩個(gè)類(lèi)是由同一個(gè)類(lèi)加載器加載的前提下才有意義,否則,即使這兩個(gè)類(lèi)來(lái)源于同一個(gè)*.class*文件,被同一個(gè)Java虛擬機(jī)加載,只要加載它們的類(lèi)加載器不同,那這兩個(gè)類(lèi)就必定不相等。
這里所指的“相等”,包括代表類(lèi)的Class對(duì)象的equals()方法、isAssignableFrom()方法、isInstance() 方法的返回結(jié)果,也包括了使用instanceof關(guān)鍵字做對(duì)象所屬關(guān)系判定等各種情況。如果沒(méi)有注意到類(lèi)加載器的影響,在某些情況下可能會(huì)產(chǎn)生具有迷惑性的結(jié)果,請(qǐng)看如下代碼
/**
* 類(lèi)加載器與instanceof關(guān)鍵字演示
*
*/
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof org.fenixsoft.classloading.ClassLoaderTest);
}
}
/*
運(yùn)行結(jié)果:
class org.fenixsoft.classloading.ClassLoaderTest
false
*/
兩行輸出結(jié)果中,從第一行可以看到這個(gè)對(duì)象確實(shí)是類(lèi)org.fenixsoft.classloading.ClassLoaderTest實(shí)例化出來(lái)的,但在第二行的輸出中卻發(fā)現(xiàn)這個(gè)對(duì)象與類(lèi)org.fenixsoft.classloading.ClassLoaderTest做所屬類(lèi)型檢查的時(shí)候返回了false。這是因?yàn)镴ava虛擬機(jī)中同時(shí)存在了兩個(gè)ClassLoaderTest類(lèi),一個(gè)是由虛擬機(jī)的應(yīng)用程序類(lèi)加載器所加載的,另外一個(gè)是由我們自定義的類(lèi)加載器加載的,雖然它們都來(lái)自同一個(gè)Class文件,但在Java虛擬機(jī)中仍然是兩個(gè)互相獨(dú)立的類(lèi),做對(duì)象所屬類(lèi)型檢查時(shí)的結(jié)果自然為false。
雙親委派模型
站在Java虛擬機(jī)的角度來(lái)看,只存在兩種不同的類(lèi)加載器:
一種是啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader),這個(gè)類(lèi)加載器使用C++語(yǔ)言實(shí)現(xiàn),是虛擬機(jī)自身的一部分;另外一種就是其他所有的類(lèi)加載器,這些類(lèi)加載器都由Java語(yǔ)言實(shí)現(xiàn),獨(dú)立存在于虛擬機(jī)外部,并且全都繼承自抽象類(lèi)java.lang.ClassLoader。 站在Java開(kāi)發(fā)人員的角度來(lái)看,類(lèi)加載器就應(yīng)當(dāng)劃分得更細(xì)致一些。自JDK 1.2以來(lái),Java一直保持著三層類(lèi)加載器、雙親委派的類(lèi)加載架構(gòu),盡管這套架構(gòu)在Java模塊化系統(tǒng)出現(xiàn)后有了一些調(diào)整變動(dòng),但依然未改變其主體結(jié)構(gòu),
三層類(lèi)加載器
啟動(dòng)類(lèi)加載器(Bootstrap Class Loader):
前面已經(jīng)介紹過(guò),這個(gè)類(lèi)加載器負(fù)責(zé)加載存放在*
擴(kuò)展類(lèi)加載器(Extension Class Loader): 這個(gè)類(lèi)加載器是在類(lèi)sun.misc.Launcher$ExtClassLoader 中以Java代碼的形式實(shí)現(xiàn)的。它負(fù)責(zé)加載JAVA_HOME>\lib 目錄中,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中所有的類(lèi)庫(kù)。根據(jù)“擴(kuò)展類(lèi)加載器”這個(gè)名稱(chēng),就可以推斷出這是一種Java系統(tǒng)類(lèi)庫(kù)的擴(kuò)展機(jī)制,JDK的開(kāi)發(fā)團(tuán)隊(duì)允許用戶(hù)將具有通用性的類(lèi)庫(kù)放置在ext目錄里以擴(kuò)展Java SE的功能,在JDK 9之后,這種擴(kuò)展機(jī)制被模塊化帶來(lái)的天然的擴(kuò)展能力所取代。由于擴(kuò)展類(lèi)加載器是由Java代碼實(shí)現(xiàn)的,開(kāi)發(fā)者可以直接在程序中使用擴(kuò)展類(lèi)加載器來(lái)加載*.class*文件。 應(yīng)用程序類(lèi)加載器(Application Class Loader): 這個(gè)類(lèi)加載器由sun.misc.Launcher$AppClassLoader來(lái)實(shí)現(xiàn)。由于應(yīng)用程序類(lèi)加載器是ClassLoader類(lèi)中的getSystemClassLoader()方法的返回值,所以有些場(chǎng)合中也稱(chēng)它為“系統(tǒng)類(lèi)加載器”。它負(fù)責(zé)加載用戶(hù)類(lèi)路徑(ClassPath)上所有的類(lèi)庫(kù),開(kāi)發(fā)者同樣可以直接在代碼中使用這個(gè)類(lèi)加載器。如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類(lèi)加載器,一般情況下這個(gè)就是程序中默認(rèn)的類(lèi)加載器。
雙親委派模型的機(jī)制
如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類(lèi),而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器去完成,每一個(gè)層次的類(lèi)加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到最頂層的啟動(dòng)類(lèi)加載器中,
只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒(méi)有找到所需的類(lèi))時(shí),子加載器才會(huì)嘗試自己去完成加載。
作用:
Java中的類(lèi)隨著它的類(lèi)加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如類(lèi)java.lang.Object,它存放在rt.jar之中,無(wú)論哪一個(gè)類(lèi)加載器要加載這個(gè)類(lèi),最終都是委派給處于模型最頂端的啟動(dòng)類(lèi)加載器進(jìn)行加載,因此Object類(lèi)在程序的各種類(lèi)加載器環(huán)境中都能夠保證是同一個(gè)類(lèi)。反之,如果沒(méi)有使用雙親委派模型,都由各個(gè)類(lèi)加載器自行去加載的話,如果用戶(hù)自己也編寫(xiě)了一個(gè)名為java.lang.Object的類(lèi),并放在程序的ClassPath中,那系統(tǒng)中就會(huì)出現(xiàn)多個(gè)不同的Object類(lèi),Java類(lèi)型體系中最基礎(chǔ)的行為也就無(wú)從保證,應(yīng)用程序?qū)?huì)變得一片混亂。
柚子快報(bào)邀請(qǐng)碼778899分享:java JVM高頻面試點(diǎn)
文章來(lái)源
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。