Java基礎(chǔ)(四)

public static int v = 8080;JVM

JVM

基本概念

JVM 是可運(yùn)行 Java 代碼的假想計(jì)算機(jī) ,包括一套字節(jié)碼指令集、一組寄存器、一個(gè)棧、
一個(gè)垃圾回收,堆 和 一個(gè)存儲(chǔ)方法域。
JVM 是運(yùn)行在操作系統(tǒng)之上的,它與硬件沒有直接
的交互。

運(yùn)行過程:

Java 源文件,通過編譯器,能夠生產(chǎn)相應(yīng)的.Class 文件,也就是字節(jié)碼文件,
而字節(jié)碼文件又通過 Java 虛擬機(jī)中的解釋器,編譯成特定機(jī)器上的機(jī)器碼 。

也就是如下:

① Java 源文件—->編譯器—->字節(jié)碼文件
② 字節(jié)碼文件—->JVM—->機(jī)器碼

每一種平臺(tái)的解釋器是不同的,但是實(shí)現(xiàn)的虛擬機(jī)是相同的,這也就是 Java 為什么能夠
跨平臺(tái)的原因了 ,當(dāng)一個(gè)程序從開始運(yùn)行,這時(shí)虛擬機(jī)就開始實(shí)例化了,多個(gè)程序啟動(dòng)就會(huì)
存在多個(gè)虛擬機(jī)實(shí)例。程序退出或者關(guān)閉,則虛擬機(jī)實(shí)例消亡,多個(gè)虛擬機(jī)實(shí)例之間數(shù)據(jù)不
能共享。

線程

這里所說的線程指程序執(zhí)行過程中的一個(gè)線程實(shí)體。JVM 允許一個(gè)應(yīng)用并發(fā)執(zhí)行多個(gè)線程。
Hotspot JVM 中的 Java 線程與原生操作系統(tǒng)線程有直接的映射關(guān)系。當(dāng)線程本地存儲(chǔ)、緩
沖區(qū)分配、同步對(duì)象、棧、程序計(jì)數(shù)器等準(zhǔn)備好以后,就會(huì)創(chuàng)建一個(gè)操作系統(tǒng)原生線程。
Java 線程結(jié)束,原生線程隨之被回收。操作系統(tǒng)負(fù)責(zé)調(diào)度所有線程,并把它們分配到任何可
用的 CPU 上。當(dāng)原生線程初始化完畢,就會(huì)調(diào)用 Java 線程的 run() 方法。當(dāng)線程結(jié)束時(shí)會(huì)釋放原生線程和 Java 線程的所有資源。

Hotspot JVM 后臺(tái)運(yùn)行的系統(tǒng)線程主要有下面幾個(gè):

JVM 內(nèi)存區(qū)域

JVM 內(nèi)存區(qū)域主要分為線程私有區(qū)域【程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法區(qū)】、線程共享區(qū)域【JAVA 堆、方法區(qū)】、直接內(nèi)存。

線程私有數(shù)據(jù)區(qū)域生命周期與線程相同, 依賴用戶線程的啟動(dòng)/結(jié)束 而 創(chuàng)建/銷毀(在 Hotspot
VM 內(nèi), 每個(gè)線程都與操作系統(tǒng)的本地線程直接映射, 因此這部分內(nèi)存區(qū)域的存/否跟隨本地線程的生/死對(duì)應(yīng))。

線程共享區(qū)域隨虛擬機(jī)的啟動(dòng)/關(guān)閉而創(chuàng)建/銷毀。

直接內(nèi)存并不是 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分, 但也會(huì)被頻繁的使用: 在 JDK 1.4 引入的 NIO 提
供了基于 Channel 與 Buffer 的 IO 方式, 它可以使用 Native 函數(shù)庫直接分配堆外內(nèi)存, 然后使用
DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作(詳見: Java I/O 擴(kuò)展), 這樣就避免了在 Java
堆和 Native 堆中來回復(fù)制數(shù)據(jù), 因此在一些場(chǎng)景中可以顯著提高性能。

程序計(jì)數(shù)器(線程私有)

一塊較小的內(nèi)存空間, 是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,每條線程都要有一個(gè)獨(dú)立的
程序計(jì)數(shù)器,這類內(nèi)存也稱為“線程私有”的內(nèi)存。

正在執(zhí)行 java 方法的話,計(jì)數(shù)器記錄的是虛擬機(jī)字節(jié)碼指令的地址(當(dāng)前指令的地址)。如
果還是 Native 方法,則為空。

這個(gè)內(nèi)存區(qū)域是唯一一個(gè)在虛擬機(jī)中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域。

虛擬機(jī)棧(線程私有)

是描述java方法執(zhí)行的內(nèi)存模型,每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)
用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成
的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。

棧幀( Frame)是用來存儲(chǔ)數(shù)據(jù)和部分過程結(jié)果的數(shù)據(jù)結(jié)構(gòu),同時(shí)也被用來處理動(dòng)態(tài)鏈接
(Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。棧幀隨著方法調(diào)用而創(chuàng)建,隨著方法結(jié)束而銷毀——無論方法是正常完成還是異常完成(拋出了在方法內(nèi)未被捕獲的異
常)都算作方法結(jié)束。

本地方法區(qū)(線程私有)

本地方法區(qū)和 Java Stack 作用類似, 區(qū)別是虛擬機(jī)棧為執(zhí)行 Java 方法服務(wù), 而本地方法棧則為
Native 方法服務(wù), 如果一個(gè) VM 實(shí)現(xiàn)使用 C-linkage 模型來支持 Native 調(diào)用, 那么該棧將會(huì)是一個(gè)
C 棧,但 HotSpot VM 直接就把本地方法棧和虛擬機(jī)棧合二為一。

堆(Heap-線程共享)-運(yùn)行時(shí)數(shù)據(jù)區(qū)

是被線程共享的一塊內(nèi)存區(qū)域,創(chuàng)建的對(duì)象和數(shù)組都保存在 Java 堆內(nèi)存中,也是垃圾收集器進(jìn)行
垃圾收集的最重要的內(nèi)存區(qū)域。由于現(xiàn)代 VM 采用分代收集算法, 因此 Java 堆從 GC 的角度還可以
細(xì)分為: 新生代(Eden 區(qū)、From Survivor 區(qū)和 To Survivor 區(qū))和老年代。

方法區(qū)/永久代(線程共享)

即我們常說的永久代(Permanent Generation), 用于存儲(chǔ)被 JVM 加載的類信息、常量、靜
態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù). HotSpot VM把GC分代收集擴(kuò)展至方法區(qū), 即使用Java堆的永久代來實(shí)現(xiàn)方法區(qū), 這樣 HotSpot 的垃圾收集器就可以像管理 Java 堆一樣管理這部分內(nèi)存,而不必為方法區(qū)開發(fā)專

門的內(nèi)存管理器(永久帶的內(nèi)存回收的主要目標(biāo)是針對(duì)常量池的回收和類型的卸載, 因此收益一般很小)。

運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class 文件中除了有類的版
本、字段、方法、接口等描述等信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中。 Java 虛擬機(jī)對(duì) Class 文件的每一部分(自然也包括常量池)的格式都有嚴(yán)格的規(guī)定,每一個(gè)字節(jié)用于存儲(chǔ)哪種數(shù)據(jù)都必須符合規(guī)范上的要求,這樣才會(huì)被虛擬機(jī)認(rèn)可、裝載和執(zhí)行。

JVM 運(yùn)行時(shí)內(nèi)存

Java 堆從 GC 的角度還可以細(xì)分為: 新生代(Eden 區(qū)、From Survivor 區(qū)和 To Survivor 區(qū))和老年代。

新生代

是用來存放新生的對(duì)象。一般占據(jù)堆的 1/3 空間。由于頻繁創(chuàng)建對(duì)象,所以新生代會(huì)頻繁觸發(fā)
MinorGC 進(jìn)行垃圾回收。新生代又分為 Eden 區(qū)、ServivorFrom、ServivorTo 三個(gè)區(qū)。

Eden 區(qū)

Java 新對(duì)象的出生地(如果新創(chuàng)建的對(duì)象占用內(nèi)存很大,則直接分配到老年代)。當(dāng) Eden 區(qū)內(nèi)存不夠的時(shí)候就會(huì)觸發(fā) MinorGC,對(duì)新生代區(qū)進(jìn)行一次垃圾回收。

ServivorFrom

上一次 GC 的幸存者,作為這一次 GC 的被掃描者。

ServivorTo

保留了一次 MinorGC 過程中的幸存者。

MinorGC 的過程(復(fù)制->清空->互換)

MinorGC 采用復(fù)制算法。

1:eden、servicorFrom 復(fù)制到 ServicorTo,年齡+1

首先,把 Eden 和 ServivorFrom 區(qū)域中存活的對(duì)象復(fù)制到 ServicorTo 區(qū)域(如果有對(duì)象的年
齡以及達(dá)到了老年的標(biāo)準(zhǔn),則賦值到老年代區(qū)),同時(shí)把這些對(duì)象的年齡+1(如果 ServicorTo 不
夠位置了就放到老年區(qū));

2:清空 eden、servicorFrom

然后,清空 Eden 和 ServicorFrom 中的對(duì)象;

3:ServicorTo 和 ServicorFrom 互換

最后,ServicorTo 和 ServicorFrom 互換,原 ServicorTo 成為下一次 GC 時(shí)的 ServicorFrom
區(qū)。

老年代

主要存放應(yīng)用程序中生命周期長(zhǎng)的內(nèi)存對(duì)象。
老年代的對(duì)象比較穩(wěn)定,所以 MajorGC 不會(huì)頻繁執(zhí)行。在進(jìn)行 MajorGC 前一般都先進(jìn)行了一次 MinorGC,使得有新生代的對(duì)象晉身入老年代,導(dǎo)致空間不夠用時(shí)才觸發(fā)。當(dāng)無法找到足
夠大的連續(xù)空間分配給新創(chuàng)建的較大對(duì)象時(shí)也會(huì)提前觸發(fā)一次 MajorGC 進(jìn)行垃圾回收騰出空間。

MajorGC 采用標(biāo)記清除算法:首先掃描一次所有老年代,標(biāo)記出存活的對(duì)象,然后回收沒
有標(biāo)記的對(duì)象。MajorGC 的耗時(shí)比較長(zhǎng),因?yàn)橐獟呙柙倩厥?。MajorGC 會(huì)產(chǎn)生內(nèi)存碎片,為了減少內(nèi)存損耗,我們一般需要進(jìn)行合并或者標(biāo)記出來方便下次直接分配。當(dāng)老年代也滿了裝不下的時(shí)候,就會(huì)拋出 OOM(Out of Memory)異常。

永久代

指內(nèi)存的永久保存區(qū)域,主要存放 Class 和 Meta(元數(shù)據(jù))的信息,Class 在被加載的時(shí)候被放入永久區(qū)域,它和和存放實(shí)例的區(qū)域不同,GC 不會(huì)在主程序運(yùn)行期對(duì)永久區(qū)域進(jìn)行清理。所以這也導(dǎo)致了永久代的區(qū)域會(huì)隨著加載的 Class 的增多而脹滿,最終拋出 OOM 異常。

JAVA8 與元數(shù)據(jù)

在 Java8 中,永久代已經(jīng)被移除,被一個(gè)稱為“元數(shù)據(jù)區(qū)”(元空間)的區(qū)域所取代。元空間
的本質(zhì)和永久代類似,元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用
本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制。類的元數(shù)據(jù)放入 native memory, 字符串池和類的靜態(tài)變量放入 java 堆中,這樣可以加載多少類的元數(shù)據(jù)就不再由
MaxPermSize 控制, 而由系統(tǒng)的實(shí)際可用空間來控制。

垃圾回收與算法

如何確定垃圾

引用計(jì)數(shù)法

在 Java 中,引用和對(duì)象是有關(guān)聯(lián)的。如果要操作對(duì)象則必須用引用進(jìn)行。因此,很顯然一個(gè)簡(jiǎn)單的辦法是通過引用計(jì)數(shù)來判斷一個(gè)對(duì)象是否可以回收。簡(jiǎn)單說,即一個(gè)對(duì)象如果沒有任何與之關(guān)聯(lián)的引用,即他們的引用計(jì)數(shù)都不為 0,則說明對(duì)象不太可能再被用到,那么這個(gè)對(duì)象就是可回收對(duì)象。

在對(duì)象頭處維護(hù)一個(gè)counter,每增加一次對(duì)該對(duì)象的引用計(jì)數(shù)器自加,如果對(duì)該對(duì)象的引用失聯(lián),則計(jì)數(shù)器自減。當(dāng)counter為0時(shí),表明該對(duì)象已經(jīng)被廢棄,不處于存活狀態(tài)。這種方式一方面無法區(qū)分軟、虛、弱、強(qiáng)引用類別。另一方面,會(huì)造成死鎖,假設(shè)兩個(gè)對(duì)象相互引用始終無法釋放counter,永遠(yuǎn)不能GC。

可達(dá)性分析

為了解決引用計(jì)數(shù)法的循環(huán)引用問題,Java 使用了可達(dá)性分析的方法。通過一系列的“GC roots”對(duì)象作為起點(diǎn)搜索。如果在“GC roots”和一個(gè)對(duì)象之間沒有可達(dá)路徑,則稱該對(duì)象是不可達(dá)的。

要注意的是,不可達(dá)對(duì)象不等價(jià)于可回收對(duì)象,不可達(dá)對(duì)象變?yōu)榭苫厥諏?duì)象至少要經(jīng)過兩次標(biāo)記
過程。兩次標(biāo)記后仍然是可回收對(duì)象,則將面臨回收。

通過一系列為GC Roots的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí),則證明該對(duì)象是不可用的。如果對(duì)象在進(jìn)行可行性分析后發(fā)現(xiàn)沒有與GC Roots相連的引用鏈,也不會(huì)理解死亡。它會(huì)暫時(shí)被標(biāo)記上并且進(jìn)行一次篩選,篩選的條件是是否與必要執(zhí)行finalize()方法。如果被判定有必要執(zhí)行finaliza()方法,就會(huì)進(jìn)入F-Queue隊(duì)列中,并有一個(gè)虛擬機(jī)自動(dòng)建立的、低優(yōu)先級(jí)的線程去執(zhí)行它。稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模標(biāo)記。如果這時(shí)還是沒有新的關(guān)聯(lián)出現(xiàn),那基本上就真的被回收了。

可達(dá)性分析算法是通過枚舉根節(jié)點(diǎn)來實(shí)現(xiàn)的,最重要的問題是GC停頓。為了確保一致性(即所有對(duì)象之間的關(guān)系是確定下來的)而導(dǎo)致GC進(jìn)行時(shí)必須進(jìn)行停頓。在HotSpot的中,使用OopMap的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)特定位置上的調(diào)試信息,存儲(chǔ)棧上那個(gè)位置原來是什么東西,這個(gè)信息是在JIT編譯時(shí)跟機(jī)器碼一起產(chǎn)生的。因?yàn)橹挥芯幾g器知道源代碼跟產(chǎn)生的代碼的對(duì)應(yīng)關(guān)系。 這樣,GC在掃描時(shí)就可以得知這些信息了。這樣做的目的是使HotSpot能夠快速準(zhǔn)確的完成GC Roots枚舉,以期望減少GC停頓所帶來的影響。HotSpot沒有在所有的指令生成OopMap,所以只是在“特定位置”記錄這些信息,這些位置就是安全點(diǎn)。程序執(zhí)行時(shí)并非在所有的位置上都能停頓下來GC,只有在到達(dá)安全點(diǎn)時(shí)才能暫停。安全點(diǎn)選取基本上是以“是否讓程序長(zhǎng)時(shí)間執(zhí)行的特征”選定。此外,HotSpot虛擬機(jī)在安全點(diǎn)的基礎(chǔ)上還增加了安全區(qū)域的概念,安全區(qū)域是安全點(diǎn)的擴(kuò)展。在一段安全區(qū)域中能夠?qū)崿F(xiàn)安全點(diǎn)不能達(dá)成的效果。

標(biāo)記清除算法(Mark-Sweep)

最基礎(chǔ)的垃圾回收算法,分為兩個(gè)階段,標(biāo)注和清除。標(biāo)記階段標(biāo)記出所有需要回收的對(duì)象,清
除階段回收被標(biāo)記的對(duì)象所占用的空間。如圖

從圖中我們就可以發(fā)現(xiàn),該算法最大的問題是內(nèi)存碎片化嚴(yán)重,后續(xù)可能發(fā)生大對(duì)象不能找到可
利用空間的問題。

復(fù)制算法(copying)

為了解決 Mark-Sweep 算法內(nèi)存碎片化的缺陷而被提出的算法。按內(nèi)存容量將內(nèi)存劃分為等大小
的兩塊。每次只使用其中一塊,當(dāng)這一塊內(nèi)存滿后將尚存活的對(duì)象復(fù)制到另一塊上去,把已使用
的內(nèi)存清掉,如圖:

這種算法雖然實(shí)現(xiàn)簡(jiǎn)單,內(nèi)存效率高,不易產(chǎn)生碎片,但是最大的問題是可用內(nèi)存被壓縮到了原
本的一半。且存活對(duì)象增多的話,Copying 算法的效率會(huì)大大降低。

標(biāo)記整理算法(Mark-Compact)

結(jié)合了以上兩個(gè)算法,為了避免缺陷而提出。標(biāo)記階段和 Mark-Sweep 算法相同,標(biāo)記后不是清
理對(duì)象,而是將存活對(duì)象移向內(nèi)存的一端。然后清除端邊界外的對(duì)象。如圖:

分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根據(jù)對(duì)象存活的不同生命周期將內(nèi)存劃分為不同的域,一般情況下將 GC 堆劃分為老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特點(diǎn)是每次垃圾回收時(shí)只有少量對(duì)象需要被回收,新生代的特點(diǎn)是每次垃圾回收時(shí)都有大量垃圾需要被回收,因此可以根據(jù)不同區(qū)域選擇不同的算法。

新生代與復(fù)制算法

目前大部分 JVM 的 GC 對(duì)于新生代都采取 Copying 算法,因?yàn)樾律忻看卫厥斩家?br> 回收大部分對(duì)象,即要復(fù)制的操作比較少,但通常并不是按照 1:1 來劃分新生代。一般將新生代
劃分為一塊較大的 Eden 空間和兩個(gè)較小的 Survivor 空間(From Space, To Space),每次使用
Eden 空間和其中的一塊 Survivor 空間,當(dāng)進(jìn)行回收時(shí),將該兩塊空間中還存活的對(duì)象復(fù)制到另
一塊 Survivor 空間中。

老年代與標(biāo)記復(fù)制算法

而老年代因?yàn)槊看沃换厥丈倭繉?duì)象,因而采用 Mark-Compact 算法。

  1. JAVA 虛擬機(jī)提到過的處于方法區(qū)的永生代(Permanet Generation),它用來存儲(chǔ) class 類,常量,方法描述等。對(duì)永生代的回收主要包括廢棄常量和無用的類。
  2. 對(duì)象的內(nèi)存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前存放對(duì)象的那一塊),少數(shù)情況會(huì)直接分配到老生代。
  3. 當(dāng)新生代的 Eden Space 和 From Space 空間不足時(shí)就會(huì)發(fā)生一次 GC,進(jìn)行 GC 后,Eden
    Space 和 From Space 區(qū)的存活對(duì)象會(huì)被挪到 To Space,然后將 Eden Space 和 From Space 進(jìn)行清理。
  4. 如果 To Space 無法足夠存儲(chǔ)某個(gè)對(duì)象,則將這個(gè)對(duì)象存儲(chǔ)到老生代。
  5. 在進(jìn)行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反復(fù)循環(huán)。
  6. 當(dāng)對(duì)象在 Survivor 區(qū)躲過一次 GC 后,其年齡就會(huì)+1。默認(rèn)情況下年齡到達(dá) 15 的對(duì)象會(huì)被
    移到老生代中。

JAVA 四中引用類型

強(qiáng)引用

在 Java 中最常見的就是強(qiáng)引用,把一個(gè)對(duì)象賦給一個(gè)引用變量,這個(gè)引用變量就是一個(gè)強(qiáng)引用。當(dāng)一個(gè)對(duì)象被強(qiáng)引用變量引用時(shí),它處于可達(dá)狀態(tài),它是不可能被垃圾回收機(jī)制回收的,即使該對(duì)象以后永遠(yuǎn)都不會(huì)被用到 JVM 也不會(huì)回收。因此強(qiáng)引用是造成 Java 內(nèi)存泄漏的主要原因之一。

軟引用

軟引用需要用 SoftReference 類來實(shí)現(xiàn),對(duì)于只有軟引用的對(duì)象來說,當(dāng)系統(tǒng)內(nèi)存足夠時(shí)它不會(huì)被回收,當(dāng)系統(tǒng)內(nèi)存空間不足時(shí)它會(huì)被回收。軟引用通常用在對(duì)內(nèi)存敏感的程序中。

弱引用

弱引用需要用 WeakReference 類來實(shí)現(xiàn),它比軟引用的生存期更短,對(duì)于只有弱引用的對(duì)象來說,只要垃圾回收機(jī)制一運(yùn)行,不管 JVM 的內(nèi)存空間是否足夠,總會(huì)回收該對(duì)象占用的內(nèi)存。

虛引用

虛引用需要 PhantomReference 類來實(shí)現(xiàn),它不能單獨(dú)使用,必須和引用隊(duì)列聯(lián)合使用。虛引用的主要作用是跟蹤對(duì)象被垃圾回收的狀態(tài)。

GC 分代收集算法 VS 分區(qū)收集算法

分代收集算法

當(dāng)前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)算法, 這種算法會(huì)根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊, 如 JVM 中的 新生代、老年代、永久代,這樣就可以根據(jù)各年代特點(diǎn)分別采用最適當(dāng)?shù)?GC 算法

在新生代-復(fù)制算法

每次垃圾收集都能發(fā)現(xiàn)大批對(duì)象已死, 只有少量存活. 因此選用復(fù)制算法, 只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集.

在老年代-標(biāo)記整理算法

因?yàn)閷?duì)象存活率高、沒有額外空間對(duì)它進(jìn)行分配擔(dān)保, 就必須采用“標(biāo)記—清理”或“標(biāo)記—整理”算法來進(jìn)行回收, 不必進(jìn)行內(nèi)存復(fù)制, 且直接騰出空閑內(nèi)存.

分區(qū)收集算法

分區(qū)算法則將整個(gè)堆空間劃分為連續(xù)的不同小區(qū)間, 每個(gè)小區(qū)間獨(dú)立使用, 獨(dú)立回收. 這樣做的
好處是可以控制一次回收多少個(gè)小區(qū)間 , 根據(jù)目標(biāo)停頓時(shí)間, 每次合理地回收若干個(gè)小區(qū)間(而不是整個(gè)堆), 從而減少一次 GC 所產(chǎn)生的停頓。

GC 垃圾收集器

Java 堆內(nèi)存被劃分為新生代和年老代兩部分,新生代主要使用復(fù)制和標(biāo)記-清除垃圾回收算法;年老代主要使用標(biāo)記-整理垃圾回收算法,因此 java 虛擬中針對(duì)新生代和年老代分別提供了多種不同的垃圾收集器,JDK1.6 中 Sun HotSpot 虛擬機(jī)的垃圾收集器如下:

Serial 垃圾收集器(單線程、復(fù)制算法)

Serial(英文連續(xù))是最基本垃圾收集器,使用復(fù)制算法,曾經(jīng)是JDK1.3.1 之前新生代唯一的垃圾收集器。Serial 是一個(gè)單線程的收集器,它不但只會(huì)使用一個(gè) CPU 或一條線程去完成垃圾收集工作,并且在進(jìn)行垃圾收集的同時(shí),必須暫停其他所有的工作線程,直到垃圾收集結(jié)束。

Serial 垃圾收集器雖然在收集垃圾過程中需要暫停所有其他的工作線程,但是它簡(jiǎn)單高效,對(duì)于限定單個(gè) CPU 環(huán)境來說,沒有線程交互的開銷,可以獲得最高的單線程垃圾收集效率,因此 Serial垃圾收集器依然是 java 虛擬機(jī)運(yùn)行在 Client 模式下默認(rèn)的新生代垃圾收集器。

ParNew 垃圾收集器(Serial+多線程)

ParNew 垃圾收集器其實(shí)是 Serial 收集器的多線程版本,也使用復(fù)制算法,除了使用多線程進(jìn)行垃圾收集之外,其余的行為和 Serial 收集器完全一樣,ParNew 垃圾收集器在垃圾收集過程中同樣也要暫停所有其他的工作線程。

ParNew 收集器默認(rèn)開啟和 CPU 數(shù)目相同的線程數(shù),可以通過-XX:ParallelGCThreads 參數(shù)來限制垃圾收集器的線程數(shù)?!綪arallel:平行的】ParNew雖然是除了多線程外和Serial 收集器幾乎完全一樣,但是ParNew垃圾收集器是很多 java虛擬機(jī)運(yùn)行在 Server 模式下新生代的默認(rèn)垃圾收集器。

Parallel Scavenge 收集器(多線程復(fù)制算法、高效)

Parallel Scavenge 收集器也是一個(gè)新生代垃圾收集器,同樣使用復(fù)制算法,也是一個(gè)多線程的垃圾收集器,它重點(diǎn)關(guān)注的是程序達(dá)到一個(gè)可控制的吞吐量(Thoughput,CPU 用于運(yùn)行用戶代碼的時(shí)間/CPU 總消耗時(shí)間,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)),高吞吐量可以最高效率地利用 CPU 時(shí)間,盡快地完成程序的運(yùn)算任務(wù),主要適用于在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。自適應(yīng)調(diào)節(jié)策略也是 ParallelScavenge 收集器與 ParNew 收集器的一個(gè)重要區(qū)別。

Serial Old 收集器(單線程標(biāo)記整理算法 )

Serial Old 是 Serial 垃圾收集器年老代版本,它同樣是個(gè)單線程的收集器,使用標(biāo)記-整理算法,這個(gè)收集器也主要是運(yùn)行在 Client 默認(rèn)的 java 虛擬機(jī)默認(rèn)的年老代垃圾收集器。在 Server 模式下,主要有兩個(gè)用途:

  1. 在 JDK1.5 之前版本中與新生代的 Parallel Scavenge 收集器搭配使用。
  2. 作為年老代中使用 CMS 收集器的后備垃圾收集方案。

新生代 Serial 與年老代 Serial Old 搭配垃圾收集過程圖:

新生代 Parallel Scavenge 收集器與 ParNew 收集器工作原理類似,都是多線程的收集器,都使用的是復(fù)制算法,在垃圾收集過程中都需要暫停所有的工作線程。新生代 Parallel Scavenge/ParNew 與年老代 Serial Old 搭配垃圾收集過程圖:

Parallel Old 收集器(多線程標(biāo)記整理算法)

Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多線程的標(biāo)記-整理算法,在 JDK1.6
才開始提供。
在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只
能保證新生代的吞吐量?jī)?yōu)先,無法保證整體的吞吐量,Parallel Old 正是為了在年老代同樣提供吞
吐量?jī)?yōu)先的垃圾收集器,如果系統(tǒng)對(duì)吞吐量要求比較高,可以優(yōu)先考慮新生代 Parallel Scavenge
和年老代 Parallel Old 收集器的搭配策略。
新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配運(yùn)行過程圖:

CMS 收集器(多線程標(biāo)記清除算法)

Concurrent mark sweep(CMS)收集器是一種年老代垃圾收集器,其最主要目標(biāo)是獲取最短垃圾
回收停頓時(shí)間,和其他年老代使用標(biāo)記-整理算法不同,它使用多線程的標(biāo)記-清除算法。

最短的垃圾收集停頓時(shí)間可以為交互比較高的程序提高用戶體驗(yàn)。

CMS 工作機(jī)制相比其他的垃圾收集器來說更復(fù)雜,整個(gè)過程分為以下 4 個(gè)階段:

初始標(biāo)記

只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)的對(duì)象,速度很快,仍然需要暫停所有的工作線程

并發(fā)標(biāo)記

進(jìn)行 GC Roots 跟蹤的過程,和用戶線程一起工作,不需要暫停工作線程。

重新標(biāo)記

為了修正在并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,仍然需要暫停所有的工作線程。

并發(fā)清除

清除 GC Roots 不可達(dá)對(duì)象,和用戶線程一起工作,不需要暫停工作線程。由于耗時(shí)最長(zhǎng)的并
發(fā)標(biāo)記和并發(fā)清除過程中,垃圾收集線程可以和用戶現(xiàn)在一起并發(fā)工作,所以總體上來看
CMS 收集器的內(nèi)存回收和用戶線程是一起并發(fā)地執(zhí)行。

CMS 收集器工作過程:

G1 收集器

Garbage first 垃圾收集器是目前垃圾收集器理論發(fā)展的最前沿成果,相比與 CMS 收集器,G1 收
集器兩個(gè)最突出的改進(jìn)是:

  1. 基于標(biāo)記-整理算法,不產(chǎn)生內(nèi)存碎片。
  2. 可以非常精確控制停頓時(shí)間,在不犧牲吞吐量前提下,實(shí)現(xiàn)低停頓垃圾回收。
    G1 收集器避免全區(qū)域垃圾收集,它把堆內(nèi)存劃分為大小固定的幾個(gè)獨(dú)立區(qū)域,并且跟蹤這些區(qū)域
    的垃圾收集進(jìn)度,同時(shí)在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)所允許的收集時(shí)間,優(yōu)先回收垃圾
    最多的區(qū)域。區(qū)域劃分和優(yōu)先級(jí)區(qū)域回收機(jī)制,確保 G1 收集器可以在有限時(shí)間獲得最高的垃圾收集效率。

JAVA IO/NIO

阻塞 IO 模型

最傳統(tǒng)的一種 IO 模型,即在讀寫數(shù)據(jù)過程中會(huì)發(fā)生阻塞現(xiàn)象。當(dāng)用戶線程發(fā)出 IO 請(qǐng)求之后,內(nèi)核會(huì)去查看數(shù)據(jù)是否就緒,如果沒有就緒就會(huì)等待數(shù)據(jù)就緒,而用戶線程就會(huì)處于阻塞狀態(tài),用戶線程交出 CPU。當(dāng)數(shù)據(jù)就緒之后,內(nèi)核會(huì)將數(shù)據(jù)拷貝到用戶線程,并返回結(jié)果給用戶線程,用戶線程才解除 block 狀態(tài)。典型的阻塞 IO 模型的例子為:data = socket.read();如果數(shù)據(jù)沒有就緒,就會(huì)一直阻塞在 read 方法。

非阻塞 IO 模型

當(dāng)用戶線程發(fā)起一個(gè) read 操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。如果結(jié)果是一個(gè)error 時(shí),它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次發(fā)送 read 操作。一旦內(nèi)核中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶線程的請(qǐng)求,那么它馬上就將數(shù)據(jù)拷貝到了用戶線程,然后返回。所以事實(shí)上,在非阻塞 IO 模型中,用戶線程需要不斷地詢問內(nèi)核數(shù)據(jù)是否就緒,也就說非阻塞 IO不會(huì)交出 CPU,而會(huì)一直占用 CPU。典型的非阻塞 IO 模型一般如下:

while(true){
data = socket.read();
if(data!= error){
處理數(shù)據(jù)
break;
}
}

但是對(duì)于非阻塞 IO 就有一個(gè)非常嚴(yán)重的問題,在 while 循環(huán)中需要不斷地去詢問內(nèi)核數(shù)據(jù)是否就緒,這樣會(huì)導(dǎo)致 CPU 占用率非常高,因此一般情況下很少使用 while 循環(huán)這種方式來讀取數(shù)據(jù)。

多路復(fù)用 IO 模型

多路復(fù)用 IO 模型是目前使用得比較多的模型。Java NIO 實(shí)際上就是多路復(fù)用 IO。在多路復(fù)用 IO模型中,會(huì)有一個(gè)線程不斷去輪詢多個(gè) socket 的狀態(tài),只有當(dāng) socket 真正有讀寫事件時(shí),才真正調(diào)用實(shí)際的 IO 讀寫操作。因?yàn)樵诙嗦窂?fù)用 IO 模型中,只需要使用一個(gè)線程就可以管理多個(gè)socket,系統(tǒng)不需要建立新的進(jìn)程或者線程,也不必維護(hù)這些線程和進(jìn)程,并且只有在真正有socket 讀寫事件進(jìn)行時(shí),才會(huì)使用 IO 資源,所以它大大減少了資源占用。

在 Java NIO 中,是通過 selector.select()去查詢每個(gè)通道是否有到達(dá)事件,如果沒有事件,則一直阻塞在那里,因此這種方式會(huì)導(dǎo)致用戶線程的阻塞。多路復(fù)用 IO 模式,通過一個(gè)線程就可以管理多個(gè) socket,只有當(dāng)socket 真正有讀寫事件發(fā)生才會(huì)占用資源來進(jìn)行實(shí)際的讀寫操作。因此,多路復(fù)用 IO 比較適合連接數(shù)比較多的情況。

另外多路復(fù)用 IO 為何比非阻塞 IO 模型的效率高是因?yàn)樵诜亲枞?IO 中,不斷地詢問 socket 狀態(tài)
時(shí)通過用戶線程去進(jìn)行的,而在多路復(fù)用 IO中,輪詢每個(gè) socket 狀態(tài)是內(nèi)核在進(jìn)行的,這個(gè)效率要比用戶線程要高的多。

不過要注意的是,多路復(fù)用 IO 模型是通過輪詢的方式來檢測(cè)是否有事件到達(dá),并且對(duì)到達(dá)的事件逐一進(jìn)行響應(yīng)。因此對(duì)于多路復(fù)用 IO 模型來說,一旦事件響應(yīng)體很大,那么就會(huì)導(dǎo)致后續(xù)的事件遲遲得不到處理,并且會(huì)影響新的事件輪詢。

信號(hào)驅(qū)動(dòng) IO 模型

在信號(hào)驅(qū)動(dòng) IO 模型中,當(dāng)用戶線程發(fā)起一個(gè) IO 請(qǐng)求操作,會(huì)給對(duì)應(yīng)的 socket 注冊(cè)一個(gè)信號(hào)函
數(shù),然后用戶線程會(huì)繼續(xù)執(zhí)行,當(dāng)內(nèi)核數(shù)據(jù)就緒時(shí)會(huì)發(fā)送一個(gè)信號(hào)給用戶線程,用戶線程接收到
信號(hào)之后,便在信號(hào)函數(shù)中調(diào)用 IO 讀寫操作來進(jìn)行實(shí)際的 IO 請(qǐng)求操作。

異步 IO 模型

異步 IO 模型才是最理想的 IO 模型,在異步 IO 模型中,當(dāng)用戶線程發(fā)起 read 操作之后,立刻就可以開始去做其它的事。而另一方面,從內(nèi)核的角度,當(dāng)它受到一個(gè) asynchronous read 之后,它會(huì)立刻返回,說明 read 請(qǐng)求已經(jīng)成功發(fā)起了,因此不會(huì)對(duì)用戶線程產(chǎn)生任何 block。然后,內(nèi)核會(huì)等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶線程,當(dāng)這一切都完成之后,內(nèi)核會(huì)給用戶線程發(fā)送一個(gè)信號(hào),告訴它 read 操作完成了。也就說用戶線程完全不需要實(shí)際的整個(gè) IO 操作是如何進(jìn)行的,只需要先發(fā)起一個(gè)請(qǐng)求,當(dāng)接收內(nèi)核返回的成功信號(hào)時(shí)表示 IO 操作已經(jīng)完成,可以直接去使用數(shù)據(jù)了。

也就說在異步 IO 模型中,IO 操作的兩個(gè)階段都不會(huì)阻塞用戶線程,這兩個(gè)階段都是由內(nèi)核自動(dòng)完成,然后發(fā)送一個(gè)信號(hào)告知用戶線程操作已完成。用戶線程中不需要再次調(diào)用 IO 函數(shù)進(jìn)行具體的讀寫。這點(diǎn)是和信號(hào)驅(qū)動(dòng)模型有所不同的,在信號(hào)驅(qū)動(dòng)模型中,當(dāng)用戶線程接收到信號(hào)表示數(shù)據(jù)已經(jīng)就緒,然后需要用戶線程調(diào)用 IO 函數(shù)進(jìn)行實(shí)際的讀寫操作;而在異步 IO 模型中,收到信號(hào)表示 IO 操作已經(jīng)完成,不需要再在用戶線程中調(diào)用 IO 函數(shù)進(jìn)行實(shí)際的讀寫操作。

注意,異步 IO 是需要操作系統(tǒng)的底層支持,在 Java 7 中,提供了 Asynchronous IO。

JAVA IO 包

JAVA NIO

NIO 主要有三大核心部分:Channel(通道),Buffer(緩沖區(qū)), Selector。傳統(tǒng) IO 基于字節(jié)流和字符流進(jìn)行操作,而 NIO 基于 Channel 和 Buffer(緩沖區(qū))進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(選擇區(qū))用于監(jiān)聽多個(gè)通道的事件(比如:連接打開,數(shù)據(jù)到達(dá))。因此,單個(gè)線程可以監(jiān)聽多個(gè)數(shù)據(jù)通道。

NIO 和傳統(tǒng) IO 之間第一個(gè)最大的區(qū)別是,IO 是面向流的,NIO 是面向緩沖區(qū)的。

NIO 的緩沖區(qū)

Java IO 面向流意味著每次從流中讀一個(gè)或多個(gè)字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,它不能前后移動(dòng)流中的數(shù)據(jù)。如果需要前后移動(dòng)從流中讀取的數(shù)據(jù),需要先將它緩存到一個(gè)緩沖區(qū)。NIO 的緩沖導(dǎo)向方法不同。數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū),需要時(shí)可在緩沖區(qū)中前后移動(dòng)。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)。而且,需確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時(shí),不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。

NIO 的非阻塞

IO 的各種流是阻塞的。這意味著,當(dāng)一個(gè)線程調(diào)用 read() 或 write()時(shí),該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。 NIO 的非阻塞模式,使一個(gè)線程從某通道發(fā)送請(qǐng)求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時(shí),就什么都不會(huì)獲取。而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此。一個(gè)線程請(qǐng)求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個(gè)線程同時(shí)可以去做別的事情。 線程通常將非阻塞 IO 的空閑時(shí)間用于在其它通道上執(zhí)行 IO 操作,所以一個(gè)單獨(dú)的線程現(xiàn)在可以管理多個(gè)輸入和輸出通道(channel)。

Channel

首先說一下 Channel,國(guó)內(nèi)大多翻譯成“通道”。Channel 和 IO 中的 Stream(流)是差不多一個(gè)
等級(jí)的。只不過 Stream 是單向的,譬如:InputStream, OutputStream,而 Channel 是雙向的,既可以用來進(jìn)行讀操作,又可以用來進(jìn)行寫操作。

NIO 中的 Channel 的主要實(shí)現(xiàn)有:

  1. FileChannel
  2. DatagramChannel
  3. SocketChannel
  4. ServerSocketChanne

這里看名字就可以猜出個(gè)所以然來:分別可以對(duì)應(yīng)文件 IO、UDP 和 TCP(Server 和 Client)。
下面演示的案例基本上就是圍繞這 4 個(gè)類型的 Channel 進(jìn)行陳述的。

Buffer

Buffer,故名思意,緩沖區(qū),實(shí)際上是一個(gè)容器,是一個(gè)連續(xù)數(shù)組。Channel 提供從文件、
網(wǎng)絡(luò)讀取數(shù)據(jù)的渠道,但是讀取或?qū)懭氲臄?shù)據(jù)都必須經(jīng)由 Buffer。

上面的圖描述了從一個(gè)客戶端向服務(wù)端發(fā)送數(shù)據(jù),然后服務(wù)端接收數(shù)據(jù)的過程??蛻舳税l(fā)送
數(shù)據(jù)時(shí),必須先將數(shù)據(jù)存入 Buffer 中,然后將 Buffer 中的內(nèi)容寫入通道。服務(wù)端這邊接收數(shù)據(jù)必須通過 Channel 將數(shù)據(jù)讀入到 Buffer 中,然后再?gòu)?Buffer 中取出數(shù)據(jù)來處理。

在 NIO 中,Buffer 是一個(gè)頂層父類,它是一個(gè)抽象類,常用的 Buffer 的子類有:
ByteBuffer、IntBuffer、 CharBuffer、 LongBuffer、 DoubleBuffer、FloatBuffer、
ShortBuffer

Selector

Selector 類是 NIO 的核心類,Selector 能夠檢測(cè)多個(gè)注冊(cè)的通道上是否有事件發(fā)生,如果有事
件發(fā)生,便獲取事件然后針對(duì)每個(gè)事件進(jìn)行相應(yīng)的響應(yīng)處理。這樣一來,只是用一個(gè)單線程就可
以管理多個(gè)通道,也就是管理多個(gè)連接。這樣使得只有在連接真正有讀寫事件發(fā)生時(shí),才會(huì)調(diào)用
函數(shù)來進(jìn)行讀寫,就大大地減少了系統(tǒng)開銷,并且不必為每個(gè)連接都創(chuàng)建一個(gè)線程,不用去維護(hù)
多個(gè)線程,并且避免了多線程之間的上下文切換導(dǎo)致的開銷。

JVM 類加載機(jī)制

JVM 類加載機(jī)制分為五個(gè)部分:加載,驗(yàn)證,準(zhǔn)備,解析,初始化,下面我們就分別來看一下這五個(gè)過程。

加載

加載是類加載過程中的一個(gè)階段,這個(gè)階段會(huì)在內(nèi)存中生成一個(gè)代表這個(gè)類的 java.lang.Class 對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的入口。注意這里不一定非得要從一個(gè) Class 文件獲取,這里既可以從 ZIP 包中讀?。ū热鐝?jar 包和 war 包中讀?。部梢栽谶\(yùn)行時(shí)計(jì)算生成(動(dòng)態(tài)代理),也可以由其它文件生成(比如將 JSP 文件轉(zhuǎn)換成對(duì)應(yīng)的 Class 類)。

驗(yàn)證

準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量的初始值階段,即在方法區(qū)中分配這些變量所使
用的內(nèi)存空間。注意這里所說的初始值概念,比如一個(gè)類變量定義為:

public static int v = 8080;

實(shí)際上變量 v 在準(zhǔn)備階段過后的初始值為 0 而不是 8080,將 v 賦值為 8080 的 put static 指令是
程序被編譯后,存放于類構(gòu)造器<client>方法之中。
但是注意如果聲明為:

public static final int v = 8080;

在編譯階段會(huì)為 v 生成 ConstantValue 屬性,在準(zhǔn)備階段虛擬機(jī)會(huì)根據(jù) ConstantValue 屬性將 v
賦值為 8080。

解析

解析階段是指虛擬機(jī)將常量池中的符號(hào)引用替換為直接引用的過程。符號(hào)引用就是 class 文件中
的:

  1. CONSTANT_Class_info
  2. CONSTANT_Field_info
  3. CONSTANT_Method_info

等類型的常量

符號(hào)引用

符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的布局無關(guān),引用的目標(biāo)并不一定要已經(jīng)加載到內(nèi)存中。各種虛擬
機(jī)實(shí)現(xiàn)的內(nèi)存布局可以各不相同,但是它們能接受的符號(hào)引用必須是一致的,因?yàn)榉?hào)引
用的字面量形式明確定義在 Java 虛擬機(jī)規(guī)范的 Class 文件格式中。

直接引用

直接引用可以是指向目標(biāo)的指針,相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。如果有
了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。

初始化

初始化階段是類加載最后一個(gè)階段,前面的類加載階段之后,除了在加載階段可以自定義類加載
器以外,其它操作都由 JVM 主導(dǎo)。到了初始階段,才開始真正執(zhí)行類中定義的 Java 程序代碼。

類構(gòu)造器<client>

初始化階段是執(zhí)行類構(gòu)造器<client>方法的過程。<client>方法是由編譯器自動(dòng)收集類中的類變量的賦值操作和靜態(tài)語句塊中的語句合并而成的。虛擬機(jī)會(huì)保證子<client>方法執(zhí)行之前,父類的<client>方法已經(jīng)執(zhí)行完畢,如果一個(gè)類中沒有對(duì)靜態(tài)變量賦值也沒有靜態(tài)語句塊,那么編譯器可以不為這個(gè)類生成<client>()方法。

注意以下幾種情況不會(huì)執(zhí)行類初始化:

  1. 通過子類引用父類的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化,而不會(huì)觸發(fā)子類的初始化。
  2. 定義對(duì)象數(shù)組,不會(huì)觸發(fā)該類的初始化。
  3. 常量在編譯期間會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用定義常量的類,不會(huì)觸發(fā)定義常量所在的類。
  4. 通過類名獲取 Class 對(duì)象,不會(huì)觸發(fā)類的初始化。
  5. 通過 Class.forName 加載指定類時(shí),如果指定參數(shù) initialize 為 false 時(shí),也不會(huì)觸發(fā)類初始化,其實(shí)這個(gè)參數(shù)是告訴虛擬機(jī),是否要對(duì)類進(jìn)行初始化。
  6. 通過 ClassLoader 默認(rèn)的 loadClass 方法,也不會(huì)觸發(fā)初始化動(dòng)作。

類加載器

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把加載動(dòng)作放到 JVM 外部實(shí)現(xiàn),以便讓應(yīng)用程序決定如何獲取所需的類,JVM 提供了 3 種類加載器:

啟動(dòng)類加載器(Bootstrap ClassLoader)

  1. 負(fù)責(zé)加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath 參數(shù)指定路徑中的,且被
    虛擬機(jī)認(rèn)可(按文件名識(shí)別,如 rt.jar)的類。

擴(kuò)展類加載器(Extension ClassLoader)

  1. 負(fù)責(zé)加載 JAVA_HOME\lib\ext 目錄中的,或通過 java.ext.dirs 系統(tǒng)變量指定路徑中的類
    庫。

應(yīng)用程序類加載器(Application ClassLoader):

  1. 負(fù)責(zé)加載用戶路徑(classpath)上的類庫。

JVM 通過雙親委派模型進(jìn)行類的加載,當(dāng)然我們也可以通過繼承 java.lang.ClassLoader
實(shí)現(xiàn)自定義的類加載器。

雙親委派

當(dāng)一個(gè)類收到了類加載請(qǐng)求,他首先不會(huì)嘗試自己去加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類去完成,每一個(gè)層次類加載器都是如此,因此所有的加載請(qǐng)求都應(yīng)該傳送到啟動(dòng)類加載其中,只有當(dāng)父類加載器反饋?zhàn)约簾o法完成這個(gè)請(qǐng)求的時(shí)候(在它的加載路徑下沒有找到所需加載的Class),子類加載器才會(huì)嘗試自己去加載。

采用雙親委派的一個(gè)好處是比如加載位于 rt.jar 包中的類 java.lang.Object,不管是哪個(gè)加載器加載這個(gè)類,最終都是委托給頂層的啟動(dòng)類加載器進(jìn)行加載,這樣就保證了使用不同的類加載器最終得到的都是同樣一個(gè) Object 對(duì)象。

JVM

OSGI(動(dòng)態(tài)模型系統(tǒng))

OSGi(Open Service Gateway Initiative),是面向 Java 的動(dòng)態(tài)模型系統(tǒng),是 Java 動(dòng)態(tài)化模塊化系統(tǒng)的一系列規(guī)范。

動(dòng)態(tài)改變構(gòu)造

OSGi 服務(wù)平臺(tái)提供在多種網(wǎng)絡(luò)設(shè)備上無需重啟的動(dòng)態(tài)改變構(gòu)造的功能。為了最小化耦合度和促使這些耦合度可管理,OSGi 技術(shù)提供一種面向服務(wù)的架構(gòu),它能使這些組件動(dòng)態(tài)地發(fā)現(xiàn)對(duì)方。

模塊化編程與熱插拔

OSGi 旨在為實(shí)現(xiàn) Java 程序的模塊化編程提供基礎(chǔ)條件,基于 OSGi 的程序很可能可以實(shí)現(xiàn)模塊級(jí)的熱插拔功能,當(dāng)程序升級(jí)更新時(shí),可以只停用、重新安裝然后啟動(dòng)程序的其中一部分,這對(duì)企業(yè)級(jí)程序開發(fā)來說是非常具有誘惑力的特性。

OSGi 描繪了一個(gè)很美好的模塊化開發(fā)目標(biāo),而且定義了實(shí)現(xiàn)這個(gè)目標(biāo)的所需要服務(wù)與架構(gòu),同時(shí)也有成熟的框架進(jìn)行實(shí)現(xiàn)支持。但并非所有的應(yīng)用都適合采用 OSGi 作為基礎(chǔ)架構(gòu),它在提供強(qiáng)大功能同時(shí),也引入了額外的復(fù)雜度,因?yàn)樗蛔袷亓祟惣虞d的雙親委托模型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,484評(píng)論 1 34
  • 所有知識(shí)點(diǎn)已整理成app app下載地址 J2EE 部分: 1.Switch能否用string做參數(shù)? 在 Jav...
    侯蛋蛋_閱讀 2,720評(píng)論 1 4
  • 九種基本數(shù)據(jù)類型的大小,以及他們的封裝類。(1)九種基本數(shù)據(jù)類型和封裝類 (2)自動(dòng)裝箱和自動(dòng)拆箱 什么是自動(dòng)裝箱...
    關(guān)瑋琳linSir閱讀 2,077評(píng)論 0 47
  • 1、java虛擬機(jī)發(fā)展史 1.1 Sun Classic jdk1.0-jdk1.4只能用解釋器方式解...
    茨菇雪菜閱讀 453評(píng)論 0 0
  • Java SE 基礎(chǔ): 封裝、繼承、多態(tài) 封裝: 概念:就是把對(duì)象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,259評(píng)論 0 8

友情鏈接更多精彩內(nèi)容