面試必備-JVM(2)

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

6.1 分代收集算法

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

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

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

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

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

6.2 分區(qū)收集算法

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

7. GC 垃圾收集器

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

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

Serial(英文連續(xù))是最基本垃圾收集器厢蒜,使用復(fù)制算法霞势,曾經(jīng)是JDK1.3.1之前新生代唯一的垃圾收集器。Serial 是一個(gè)單線程的收集器斑鸦,它不但只會(huì)使用一個(gè) CPU 或一條線程去完成垃圾收集工
作愕贡,并且在進(jìn)行垃圾收集的同時(shí)必須暫停其他所有的工作線程鄙才,直到垃圾收集結(jié)束颂鸿。Serial 垃圾收集器雖然在收集垃圾過(guò)程中需要暫停所有其他的工作線程,但是它簡(jiǎn)單高效攒庵,對(duì)于限定單個(gè) CPU 環(huán)境來(lái)說(shuō)嘴纺,沒(méi)有線程交互的開(kāi)銷,可以獲得最高的單線程垃圾收集效率浓冒,因此 Serial垃圾收集器依然是 java 虛擬機(jī)運(yùn)行在 Client 模式下默認(rèn)的新生代垃圾收集器栽渴。

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

ParNew 垃圾收集器其實(shí)是 Serial 收集器的多線程版本,也使用復(fù)制算法稳懒,除了使用多線程進(jìn)行垃圾收集之外闲擦,其余的行為和 Serial 收集器完全一樣,ParNew 垃圾收集器在垃圾收集過(guò)程中同樣也
要暫停所有其他的工作線程场梆。ParNew 收集器默認(rèn)開(kāi)啟和 CPU 數(shù)目相同的線程數(shù)墅冷,可以通過(guò)-XX:ParallelGCThreads 參數(shù)來(lái)限
制垃圾收集器的線程數(shù)』蛴停【Parallel:平行的】ParNew雖然是除了多線程外和Serial收集器幾乎完全一樣寞忿,但是ParNew垃圾收集器是很多java虛擬機(jī)運(yùn)行在 Server 模式下新生代的默認(rèn)垃圾收集器

7.3 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ū)別藕施。

7.4 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 收集器的后備垃圾收集方案浊吏。


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


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

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

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

Concurrent mark sweep(CMS)收集器是一種年老代垃圾收集器樊破,其最主要目標(biāo)是獲取最短垃圾回收停頓時(shí)間,和其他年老代使用標(biāo)記-整理算法不同唆铐,它使用多線程的標(biāo)記-清除算法哲戚。
最短的垃圾收集停頓時(shí)間可以為交互比較高的程序提高用戶體驗(yàn)
CMS 工作機(jī)制相比其他的垃圾收集器來(lái)說(shuō)更復(fù)雜艾岂,整個(gè)過(guò)程分為以下 4 個(gè)階段:

7.6.1 初始標(biāo)記

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

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

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

7.6.3 重新標(biāo)記

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

7.6.4 并發(fā)清除

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

CMS 收集器工作過(guò)程:


7.7 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í)間獲得最高的垃圾收集效率砂代。

8. Java IO/NIO

8.1 阻塞 IO 模型

最傳統(tǒng)的一種 IO 模型蹋订,即在讀寫數(shù)據(jù)過(guò)程中會(huì)發(fā)生阻塞現(xiàn)象。當(dāng)用戶線程發(fā)出 IO 請(qǐng)求之后露戒,內(nèi)核會(huì)去查看數(shù)據(jù)是否就緒捶箱,如果沒(méi)有就緒就會(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ù)沒(méi)有就緒呀页,就會(huì)一直阻塞在 read 方法。

8.2 非阻塞 IO 模型

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

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

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

8.3 多路復(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 中废恋,是通過(guò) selector.select()去查詢每個(gè)通道是否有到達(dá)事件鱼鼓,如果沒(méi)有事件迄本,則一直阻塞在那里嘉赎,因此這種方式會(huì)導(dǎo)致用戶線程的阻塞。多路復(fù)用 IO 模式隔披,通過(guò)一個(gè)線程就可以管理多個(gè) socket抓韩,只有當(dāng)socket 真正有讀寫事件發(fā)生才會(huì)占用資源來(lái)進(jìn)行實(shí)際的讀寫操作尝江。因此炭序,多路復(fù)用 IO 比較適合連接數(shù)比較多的情況惭聂。

另外多路復(fù)用 IO 為何比非阻塞 IO 模型的效率高是因?yàn)?strong>在非阻塞 IO 中辜纲,不斷地詢問(wèn) socket 狀態(tài)時(shí)通過(guò)用戶線程去進(jìn)行的耕腾,而在多路復(fù)用 IO 中,輪詢每個(gè) socket 狀態(tài)是內(nèi)核在進(jìn)行的牵舵,這個(gè)效率要比用戶線程要高的多倦挂。

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

8.4 信號(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 讀寫操作來(lái)進(jìn)行實(shí)際的 IO 請(qǐng)求操作浦辨。

8.5 異步 IO 模型

異步 IO 模型才是最理想的 IO 模型流酬,在異步 IO 模型中芽腾,當(dāng)用戶線程發(fā)起 read 操作之后,立刻就可以開(kāi)始去做其它的事。而另一方面旱函,從內(nèi)核的角度,當(dāng)它收到一個(gè) asynchronous read 之后含长,它會(huì)立刻返回拘泞,說(shuō)明 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 操作完成了。也就說(shuō)用戶線程完全不需要實(shí)際的整個(gè) IO 操作是如何進(jìn)行的促脉,只需要先發(fā)起一個(gè)請(qǐng)求瘸味,當(dāng)接收內(nèi)核返回的成功信號(hào)時(shí)表示 IO 操作已經(jīng)完成藕夫,可以直接去使用數(shù)據(jù)了毅贮。

也就說(shuō)在異步 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í)際的讀寫操作玄组。

8.a 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)聽(tīng)多個(gè)通道的事件(比如:連接打開(kāi)剩瓶,數(shù)據(jù)到達(dá))。因此城丧,單個(gè)線程可以監(jiān)聽(tīng)多個(gè)數(shù)據(jù)通道延曙。

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

8.a.1 NIO 的緩沖區(qū)

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

8.a.2 NIO 的非阻塞

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

8.b Channel

首先說(shuō)一下 Channel县貌,國(guó)內(nèi)大多翻譯成“通道”术陶。Channel 和 IO 中的 Stream(流)是差不多一個(gè)等級(jí)的凑懂。只不過(guò) Stream 是單向的,譬如:InputStream, OutputStream梧宫,而 Channel 是雙向的接谨,既可以用來(lái)進(jìn)行讀操作,又可以用來(lái)進(jìn)行寫操作塘匣。

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

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

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

8.c 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ù)的過(guò)程浦马。客戶端發(fā)送數(shù)據(jù)時(shí),必須先將數(shù)據(jù)存入 Buffer 中张漂,然后將 Buffer 中的內(nèi)容寫入通道晶默。服務(wù)端這邊接收數(shù)據(jù)必須通過(guò) Channel 將數(shù)據(jù)讀入到 Buffer 中,然后再從 Buffer 中取出數(shù)據(jù)來(lái)處理航攒。

在 NIO 中荤胁,Buffer 是一個(gè)頂層父類,它是一個(gè)抽象類屎债,常用的 Buffer 的子類有:ByteBuffer仅政、IntBuffer、 CharBuffer盆驹、 LongBuffer圆丹、 DoubleBuffer、FloatBuffer躯喇、ShortBuffer

8.d Selector

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

9 JVM 類加載機(jī)制

JVM 類加載機(jī)制分為五個(gè)部分:加載栖博,驗(yàn)證,準(zhǔn)備仇让,解析,初始化丧叽,下面我們就分別來(lái)看一下這五個(gè)過(guò)程获枝。


9.1 加載

加載是類加載過(guò)程中的一個(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 類)粗俱。

9.2 驗(yàn)證

這一階段的主要目的是為了確保 Class 文件的字節(jié)流中包含的信息是否符合當(dāng)前虛擬機(jī)的要求说榆,并且不會(huì)危害虛擬機(jī)自身的安全。

9.3 準(zhǔn)備

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

public static int v = 8080;

實(shí)際上變量 v 在準(zhǔn)備階段過(guò)后的初始值為 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神汹。

9.4 解析

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

  1. CONSTANT_Class_info
  2. CONSTANT_Field_info
  3. CONSTANT_Method_info

等類型的常量。

9.5 符號(hào)引用

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

9.6 直接引用

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

9.7 初始化

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

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

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

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

  1. 通過(guò)子類引用父類的靜態(tài)字段饰剥,只會(huì)觸發(fā)父類的初始化汰蓉,而不會(huì)觸發(fā)子類的初始化顾孽。
  2. 定義對(duì)象數(shù)組若厚,不會(huì)觸發(fā)該類的初始化。
  3. 常量在編譯期間會(huì)存入調(diào)用類的常量池中霎冯,本質(zhì)上并沒(méi)有直接引用定義常量的類岗憋,不會(huì)觸發(fā)定義常量所在的類关串。
  4. 通過(guò)類名獲取 Class 對(duì)象监徘,不會(huì)觸發(fā)類的初始化墓卦。
  5. 通過(guò) Class.forName 加載指定類時(shí)落剪,如果指定參數(shù) initialize 為 false 時(shí)忠怖,也不會(huì)觸發(fā)類初始化凡泣,其實(shí)這個(gè)參數(shù)是告訴虛擬機(jī)鞋拟,是否要對(duì)類進(jìn)行初始化贺纲。
  6. 通過(guò) ClassLoader 默認(rèn)的 loadClass 方法哮笆,也不會(huì)觸發(fā)初始化動(dòng)作稠肘。

9.a 類加載器

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

9.a.1 啟動(dòng)類加載器(Bootstrap ClassLoader)

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

9.a.2 擴(kuò)展類加載器(Extension ClassLoader)

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

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

  1. 負(fù)責(zé)加載用戶路徑(classpath)上的類庫(kù)谦絮。
    JVM 通過(guò)雙親委派模型進(jìn)行類的加載层皱,當(dāng)然我們也可以通過(guò)繼承 java.lang.ClassLoader實(shí)現(xiàn)自定義的類加載器。

9.c 雙親委派

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

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

9.d 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ī)范。

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

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

9.d.1 模塊化編程與熱插拔

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

OSGi 描繪了一個(gè)很美好的模塊化開(kāi)發(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)樗?strong>不遵守了類加載的雙親委托模型蚕断。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市入挣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挂滓,死亡現(xiàn)場(chǎng)離奇詭異贝椿,居然都是意外死亡踪栋,警方通過(guò)查閱死者的電腦和手機(jī)囤官,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門蹲堂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)朽基,“玉大人渡蜻,你說(shuō)我怎么就攤上這事。” “怎么了侈离?”我有些...
    開(kāi)封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵绿映,是天一觀的道長(zhǎng)钝诚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)腺怯,這世上最難降的妖魔是什么仅叫? 我笑而不...
    開(kāi)封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任幻锁,我火速辦了婚禮鸣戴,結(jié)果婚禮上入偷,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好锋爪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著崎坊,像睡著了一般顽爹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上镜粤,一...
    開(kāi)封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天捏题,我揣著相機(jī)與錄音,去河邊找鬼繁仁。 笑死涉馅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的黄虱。 我是一名探鬼主播稚矿,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼捻浦!你這毒婦竟也來(lái)了晤揣?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤朱灿,失蹤者是張志新(化名)和其女友劉穎昧识,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盗扒,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跪楞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年缀去,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甸祭。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缕碎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出池户,到底是詐尸還是另有隱情咏雌,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布校焦,位于F島的核電站赊抖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏寨典。R本人自食惡果不足惜氛雪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凝赛。 院中可真熱鬧注暗,春花似錦、人聲如沸墓猎。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毙沾。三九已至骗卜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間左胞,已是汗流浹背寇仓。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烤宙,地道東北人遍烦。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像躺枕,于是被迫代替她去往敵國(guó)和親服猪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容