時(shí)間:2023-07-02 04:48:01 | 來源:網(wǎng)站運(yùn)營
時(shí)間:2023-07-02 04:48:01 來源:網(wǎng)站運(yùn)營
Java虛擬機(jī)(JVM)你只要看這一篇就夠了?。罕疚氖菍W(xué)習(xí)了《深入理解Java虛擬機(jī)》之后的總結(jié),主要內(nèi)容都來自于書中,也有作者的一些理解。一是為了梳理知識點(diǎn),歸納總結(jié),二是為了分享交流,如有錯(cuò)誤之處還望指出。根據(jù)《Java 虛擬機(jī)規(guī)范(Java SE 7 版)》規(guī)定,Java 虛擬機(jī)所管理的內(nèi)存如下圖所示。
內(nèi)存空間小,線程私有。字節(jié)碼解釋器工作是就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行指令的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴計(jì)數(shù)器完成如果線程正在執(zhí)行一個(gè) Java 方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是 Native 方法,這個(gè)計(jì)數(shù)器的值則為 (Undefined)。此內(nèi)存區(qū)域是唯一一個(gè)在 Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域。
線程私有,生命周期和線程一致。描述的是 Java 方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行時(shí)都會(huì)床創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲局部變量表:存放了編譯期可知的各種基本類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型)和 returnAddress 類型(指向了一條字節(jié)碼指令的地址)局部變量表
、操作數(shù)棧
、動(dòng)態(tài)鏈接
、方法出口
等信息。每一個(gè)方法從調(diào)用直至執(zhí)行結(jié)束,就對應(yīng)著一個(gè)棧幀從虛擬機(jī)棧中入棧到出棧的過程。
區(qū)別于 Java 虛擬機(jī)棧的是,Java 虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)。也會(huì)有 StackOverflowError 和 OutOfMemoryError 異常。1.1.4 Java 堆
對于絕大多數(shù)應(yīng)用來說,這塊區(qū)域是 JVM 所管理的內(nèi)存中最大的一塊。線程共享,主要是存放對象實(shí)例和數(shù)組。內(nèi)部會(huì)劃分出多個(gè)線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer, TLAB)??梢晕挥谖锢砩喜贿B續(xù)的空間,但是邏輯上要連續(xù)。OutOfMemoryError:如果堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法再擴(kuò)展時(shí),拋出該異常。
屬于共享內(nèi)存區(qū)域,存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。現(xiàn)在用一張圖來介紹每個(gè)區(qū)域存儲的內(nèi)容。
屬于方法區(qū)一部分,用于存放編譯期生成的各種字面量和符號引用。編譯器和運(yùn)行期(String 的 intern() )都可以將常量放入池中。內(nèi)存有限,無法申請時(shí)拋出 OutOfMemoryError。1.1.7 直接內(nèi)存
非虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的部分在 JDK 1.4 中新加入 NIO (New Input/Output) 類,引入了一種基于通道(Channel)和緩存(Buffer)的 I/O 方式,它可以使用 Native 函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲在 Java 堆中的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進(jìn)行操作??梢员苊庠?Java 堆和 Native 堆中來回的數(shù)據(jù)耗時(shí)操作。
主要介紹數(shù)據(jù)是如何創(chuàng)建、如何布局以及如何訪問的。1.2.1 對象的創(chuàng)建
創(chuàng)建過程比較復(fù)雜,建議看書了解,這里提供個(gè)人的總結(jié)。遇到 new 指令時(shí),首先檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號引用,并且檢查這個(gè)符號引用代表的類是否已經(jīng)被加載、解析和初始化過。如果沒有,執(zhí)行相應(yīng)的類加載。
在 HotSpot 虛擬機(jī)中,分為 3 塊區(qū)域:對象頭(Header)
、實(shí)例數(shù)據(jù)(Instance Data)
和對齊填充(Padding)
對象頭(Header)
:包含兩部分,第一部分用于存儲對象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼、GC 分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程 ID、偏向時(shí)間戳等,32 位虛擬機(jī)占 32 bit,64 位虛擬機(jī)占 64 bit。官方稱為 ‘Mark Word’。第二部分是類型指針,即對象指向它的類的元數(shù)據(jù)指針,虛擬機(jī)通過這個(gè)指針確定這個(gè)對象是哪個(gè)類的實(shí)例。另外,如果是 Java 數(shù)組,對象頭中還必須有一塊用于記錄數(shù)組長度的數(shù)據(jù),因?yàn)槠胀▽ο罂梢酝ㄟ^ Java 對象元數(shù)據(jù)確定大小,而數(shù)組對象不可以。實(shí)例數(shù)據(jù)(Instance Data)
:程序代碼中所定義的各種類型的字段內(nèi)容(包含父類繼承下來的和子類中定義的)。對齊填充(Padding)
:不是必然需要,主要是占位,保證對象大小是某個(gè)字節(jié)的整數(shù)倍。使用對象時(shí),通過棧上的 reference 數(shù)據(jù)來操作堆上的具體對象。通過句柄訪問
Java 堆中會(huì)分配一塊內(nèi)存作為句柄池。reference 存儲的是句柄地址。詳情見圖。
reference 中直接存儲對象地址
// 待填
程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧 3 個(gè)區(qū)域隨線程生滅(因?yàn)槭蔷€程私有),棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行著出棧和入棧操作。而 Java 堆和方法區(qū)則不一樣,一個(gè)接口中的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能不一樣,一個(gè)方法中的多個(gè)分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運(yùn)行期才知道那些對象會(huì)創(chuàng)建,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的,垃圾回收期所關(guān)注的就是這部分內(nèi)存。
在進(jìn)行內(nèi)存回收之前要做的事情就是判斷那些對象是‘死’的,哪些是‘活’的。2.2.1 引用計(jì)數(shù)法
給對象添加一個(gè)引用計(jì)數(shù)器。但是難以解決循環(huán)引用問題。
通過一系列的 ‘GC Roots’ 的對象作為起始點(diǎn),從這些節(jié)點(diǎn)出發(fā)所走過的路徑稱為引用鏈。當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連的時(shí)候說明對象不可用。
前面的兩種方式判斷存活時(shí)都與‘引用’有關(guān)。但是 JDK 1.2 之后,引用概念進(jìn)行了擴(kuò)充,下面具體介紹。下面四種引用強(qiáng)度一次逐漸減弱
類似于 Object obj = new Object();
創(chuàng)建的,只要強(qiáng)引用在就不回收。
軟引用SoftReference 類實(shí)現(xiàn)軟引用。在系統(tǒng)要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對象列進(jìn)回收范圍之中進(jìn)行二次回收。弱引用
WeakReference 類實(shí)現(xiàn)弱引用。對象只能生存到下一次垃圾收集之前。在垃圾收集器工作時(shí),無論內(nèi)存是否足夠都會(huì)回收掉只被弱引用關(guān)聯(lián)的對象。虛引用
PhantomReference 類實(shí)現(xiàn)虛引用。無法通過虛引用獲取一個(gè)對象的實(shí)例,為一個(gè)對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。2.2.4 生存還是死亡
即使在可達(dá)性分析算法中不可達(dá)的對象,也并非是“facebook”的,這時(shí)候它們暫時(shí)出于“緩刑”階段,一個(gè)對象的真正死亡至少要經(jīng)歷兩次標(biāo)記過程:如果對象在進(jìn)行中可達(dá)性分析后發(fā)現(xiàn)沒有與 GC Roots 相連接的引用鏈,那他將會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選,篩選條件是此對象是否有必要執(zhí)行 finalize() 方法。當(dāng)對象沒有覆蓋 finalize() 方法,或者 finalize() 方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”。2.2.5 回收方法區(qū)
如果這個(gè)對象被判定為有必要執(zhí)行 finalize() 方法,那么這個(gè)對象竟會(huì)放置在一個(gè)叫做 F-Queue 的隊(duì)列中,并在稍后由一個(gè)由虛擬機(jī)自動(dòng)建立的、低優(yōu)先級的 Finalizer 線程去執(zhí)行它。這里所謂的“執(zhí)行”是指虛擬機(jī)會(huì)出發(fā)這個(gè)方法,并不承諾或等待他運(yùn)行結(jié)束。finalize() 方法是對象逃脫死亡命運(yùn)的最后一次機(jī)會(huì),稍后 GC 將對 F-Queue 中的對象進(jìn)行第二次小規(guī)模的標(biāo)記,如果對象要在 finalize() 中成功拯救自己 —— 只要重新與引用鏈上的任何一個(gè)對象簡歷關(guān)聯(lián)即可。
finalize() 方法只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次。
在堆中,尤其是在新生代中,一次垃圾回收一般可以回收 70% ~ 95% 的空間,而永久代的垃圾收集效率遠(yuǎn)低于此。判斷廢棄常量:一般是判斷沒有該常量的引用。
永久代垃圾回收主要兩部分內(nèi)容:廢棄的常量和無用的類。
僅提供思路2.3.1 標(biāo)記 —— 清除算法
直接標(biāo)記清除就可。兩個(gè)不足:
把空間分成兩塊,每次只對其中一塊進(jìn)行 GC。當(dāng)這塊內(nèi)存使用完時(shí),就將還存活的對象復(fù)制到另一塊上面。解決前一種方法的不足,但是會(huì)造成空間利用率低下。因?yàn)榇蠖鄶?shù)新生代對象都不會(huì)熬過第一次 GC。所以沒必要 1 : 1 劃分空間。可以分一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 空間和其中一塊 Survivor。當(dāng)回收時(shí),將 Eden 和 Survivor 中還存活的對象一次性復(fù)制到另一塊 Survivor 上,最后清理 Eden 和 Survivor 空間。大小比例一般是 8 : 1 : 1,每次浪費(fèi) 10% 的 Survivor 空間。但是這里有一個(gè)問題就是如果存活的大于 10% 怎么辦?這里采用一種分配擔(dān)保策略:多出來的對象直接進(jìn)入老年代。
不同于針對新生代的復(fù)制算法,針對老年代的特點(diǎn),創(chuàng)建該算法。主要是把存活對象移到內(nèi)存的一端。2.3.4 分代回收
根據(jù)存活對象劃分幾塊內(nèi)存區(qū),一般是分為新生代和老年代。然后根據(jù)各個(gè)年代的特點(diǎn)制定相應(yīng)的回收算法。新生代
每次垃圾回收都有大量對象死去,只有少量存活,選用復(fù)制算法比較合理。老年代
老年代中對象存活率較高、沒有額外的空間分配對它進(jìn)行擔(dān)保。所以必須使用標(biāo)記 —— 清除
或者標(biāo)記 —— 整理
算法回收。
// 待填
收集算法是內(nèi)存回收的理論,而垃圾回收器是內(nèi)存回收的實(shí)踐。
這是一個(gè)單線程收集器。意味著它只會(huì)使用一個(gè) CPU 或一條收集線程去完成收集工作,并且在進(jìn)行垃圾回收時(shí)必須暫停其它所有的工作線程直到收集結(jié)束。
可以認(rèn)為是 Serial 收集器的多線程版本。
指多條垃圾收集線程并行工作,此時(shí)用戶線程處于等待狀態(tài)并發(fā):Concurrent
指用戶線程和垃圾回收線程同時(shí)執(zhí)行(不一定是并行,有可能是交叉執(zhí)行),用戶進(jìn)程在運(yùn)行,而垃圾回收線程在另一個(gè) CPU 上運(yùn)行。2.5.3 Parallel Scavenge 收集器
這是一個(gè)新生代收集器,也是使用復(fù)制算法實(shí)現(xiàn),同時(shí)也是并行的多線程收集器。CMS 等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程所停頓的時(shí)間,而 Parallel Scavenge 收集器的目的是達(dá)到一個(gè)可控制的吞吐量(Throughput = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間))。
收集器的老年代版本,單線程,使用 標(biāo)記 —— 整理
。
Parallel Old 是 Parallel Scavenge 收集器的老年代版本。多線程,使用 標(biāo)記 —— 整理
CMS (Concurrent Mark Sweep) 收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器?;?標(biāo)記 —— 清除
算法實(shí)現(xiàn)。
運(yùn)作步驟:標(biāo)記 —— 清除
算法帶來的空間碎片面向服務(wù)端的垃圾回收器。優(yōu)點(diǎn):并行與并發(fā)、分代收集、空間整合、可預(yù)測停頓。
對象主要分配在新生代的 Eden 區(qū)上,如果啟動(dòng)了本地線程分配緩沖區(qū),將線程優(yōu)先在 (TLAB) 上分配。少數(shù)情況會(huì)直接分配在老年代中。一般來說 Java 堆的內(nèi)存模型如下圖所示:
發(fā)生在新生代的垃圾回收動(dòng)作,頻繁,速度快。老年代 GC (Major GC / Full GC)
發(fā)生在老年代的垃圾回收動(dòng)作,出現(xiàn)了 Major GC 經(jīng)常會(huì)伴隨至少一次 Minor GC(非絕對)。Major GC 的速度一般會(huì)比 Minor GC 慢十倍以上。2.6.2 大對象直接進(jìn)入老年代
屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異。
關(guān)鍵字 volatile 是 Java 虛擬機(jī)提供的最輕量級的同步機(jī)制。一個(gè)變量被定義為 volatile 的特性:
如果不符合運(yùn)算結(jié)果并不依賴變量當(dāng)前值,或者能夠確保只有單一的線程修改變量的值
和變量不需要與其他的狀態(tài)變量共同參與不變約束
就要通過加鎖(使用 synchronize 或 java.util.concurrent 中的原子類)來保證原子性。
通過插入內(nèi)存屏障保證一致性。3.1.3 對于 long 和 double 型變量的特殊規(guī)則
Java 要求對于主內(nèi)存和工作內(nèi)存之間的八個(gè)操作都是原子性的,但是對于 64 位的數(shù)據(jù)類型,有一條寬松的規(guī)定:允許虛擬機(jī)將沒有被 volatile 修飾的 64 位數(shù)據(jù)的讀寫操作劃分為兩次 32 位的操作來進(jìn)行,即允許虛擬機(jī)實(shí)現(xiàn)選擇可以不保證 64 位數(shù)據(jù)類型的 load、store、read 和 write 這 4 個(gè)操作的原子性。這就是 long 和 double 的非原子性協(xié)定。3.1.4 原子性、可見性與有序性
回顧下并發(fā)下應(yīng)該注意操作的那些特性是什么,同時(shí)加深理解。
由 Java 內(nèi)存模型來直接保證的原子性變量操作包括 read、load、assign、use、store 和 write。大致可以認(rèn)為基本數(shù)據(jù)類型的操作是原子性的。同時(shí) lock 和 unlock 可以保證更大范圍操作的原子性。而 synchronize 同步塊操作的原子性是用更高層次的字節(jié)碼指令 monitorenter 和 monitorexit 來隱式操作的。
是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程也能夠立即得知這個(gè)通知。主要操作細(xì)節(jié)就是修改值后將值同步至主內(nèi)存(volatile 值使用前都會(huì)從主內(nèi)存刷新),除了 volatile 還有 synchronize 和 final 可以保證可見性。同步塊的可見性是由“對一個(gè)變量執(zhí)行 unlock 操作之前,必須先把此變量同步會(huì)主內(nèi)存中( store、write 操作)”這條規(guī)則獲得。而 final 可見性是指:被 final 修飾的字段在構(gòu)造器中一旦完成,并且構(gòu)造器沒有把 “this” 的引用傳遞出去( this 引用逃逸是一件很危險(xiǎn)的事情,其他線程有可能通過這個(gè)引用訪問到“初始化了一半”的對象),那在其他線程中就能看見 final 字段的值。
如果在被線程內(nèi)觀察,所有操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有操作都是無序的。前半句指“線程內(nèi)表現(xiàn)為串行的語義”,后半句是指“指令重排”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。Java 語言通過 volatile 和 synchronize 兩個(gè)關(guān)鍵字來保證線程之間操作的有序性。volatile 自身就禁止指令重排,而 synchronize 則是由“一個(gè)變量在同一時(shí)刻指允許一條線程對其進(jìn)行 lock 操作”這條規(guī)則獲得,這條規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行的進(jìn)入。3.1.5 先行發(fā)生原則
也就是 happens-before 原則。這個(gè)原則是判斷數(shù)據(jù)是否存在競爭、線程是否安全的主要依據(jù)。先行發(fā)生是 Java 內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系。天然的先行發(fā)生關(guān)系
直接由操作系統(tǒng)內(nèi)核支持的線程,這種線程由內(nèi)核完成切換。程序一般不會(huì)直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級接口 —— 輕量級進(jìn)程(LWP),輕量級進(jìn)程就是我們通常意義上所講的線程,每個(gè)輕量級進(jìn)程都有一個(gè)內(nèi)核級線程支持。
廣義上來說,只要不是內(nèi)核線程就可以認(rèn)為是用戶線程,因此可以認(rèn)為輕量級進(jìn)程也屬于用戶線程。狹義上說是完全建立在用戶空間的線程庫上的并且內(nèi)核系統(tǒng)不可感知的。
直接看圖
平臺不同實(shí)現(xiàn)方式不同,可以認(rèn)為是一條 Java 線程映射到一條輕量級進(jìn)程。3.2.2 Java 線程調(diào)度
線程執(zhí)行時(shí)間由線程自身控制,實(shí)現(xiàn)簡單,切換線程自己可知,所以基本沒有線程同步問題。壞處是執(zhí)行時(shí)間不可控,容易阻塞。搶占式線程調(diào)度
每個(gè)線程由系統(tǒng)來分配執(zhí)行時(shí)間。3.2.3 狀態(tài)轉(zhuǎn)換
創(chuàng)建后尚未啟動(dòng)的線程。
Runable 包括了操作系統(tǒng)線程狀態(tài)中的 Running 和 Ready,也就是出于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待 CPU 為他分配時(shí)間。
出于這種狀態(tài)的線程不會(huì)被 CPU 分配時(shí)間,它們要等其他線程顯示的喚醒。以下方法會(huì)然線程進(jìn)入無限期等待狀態(tài):
處于這種狀態(tài)的線程也不會(huì)分配時(shí)間,不過無需等待配其他線程顯示地喚醒,在一定時(shí)間后他們會(huì)由系統(tǒng)自動(dòng)喚醒。以下方法會(huì)讓線程進(jìn)入限期等待狀態(tài):
線程被阻塞了,“阻塞狀態(tài)”和“等待狀態(tài)”的區(qū)別是:“阻塞狀態(tài)”在等待著獲取一個(gè)排他鎖,這個(gè)時(shí)間將在另外一個(gè)線程放棄這個(gè)鎖的時(shí)候發(fā)生;而“等待狀態(tài)”則是在等待一段時(shí)間,或者喚醒動(dòng)作的發(fā)生。在程序等待進(jìn)入同步區(qū)域的時(shí)候,線程將進(jìn)入這種狀態(tài)。
已終止線程的線程狀態(tài)。
// 待填
// 待填有點(diǎn)懶了。。。先貼幾個(gè)網(wǎng)址吧。
虛擬機(jī)把描述類的數(shù)據(jù)從 Class 文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、裝換解析和初始化,最終形成可以被虛擬機(jī)直接使用的 Java 類型。在 Java 語言中,類型的加載、連接和初始化過程都是在程序運(yùn)行期間完成的。
public class SuperClass { static { System.out.println("SuperClass init!"); } public static int value = 1127;} public class SubClass extends SuperClass { static { System.out.println("SubClass init!"); }} public class ConstClass { static { System.out.println("ConstClass init!"); } public static final String HELLOWORLD = "hello world!"} public class NotInitialization { public static void main(String[] args) { System.out.println(SubClass.value); /** * output : SuperClass init! * * 通過子類引用父類的靜態(tài)對象不會(huì)導(dǎo)致子類的初始化 * 只有直接定義這個(gè)字段的類才會(huì)被初始化 */ SuperClass[] sca = new SuperClass[10]; /** * output : * * 通過數(shù)組定義來引用類不會(huì)觸發(fā)此類的初始化 * 虛擬機(jī)在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建了一個(gè)數(shù)組類 */ System.out.println(ConstClass.HELLOWORLD); /** * output : * * 常量在編譯階段會(huì)存入調(diào)用類的常量池當(dāng)中,本質(zhì)上并沒有直接引用到定義類常量的類, * 因此不會(huì)觸發(fā)定義常量的類的初始化。 * “hello world” 在編譯期常量傳播優(yōu)化時(shí)已經(jīng)存儲到 NotInitialization 常量池中了。 */ }}
是連接的第一步,確保 Class 文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)要求。文件格式驗(yàn)證
這個(gè)階段正式為類分配內(nèi)存并設(shè)置類變量初始值,內(nèi)存在方法去中分配(含 static 修飾的變量不含實(shí)例變量)。
public static int value = 1127;
這個(gè)階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程。
前面過程都是以虛擬機(jī)主導(dǎo),而初始化階段開始執(zhí)行類中的 Java 代碼。6.3 類加載器
通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流。6.3.1 雙親委派模型
從 Java 虛擬機(jī)角度講,只存在兩種類加載器:一種是啟動(dòng)類加載器(C++ 實(shí)現(xiàn),是虛擬機(jī)的一部分);另一種是其他所有類的加載器(Java 實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部且全繼承自 java.lang.ClassLoader)
keyword:線程上下文加載器(Thread Context ClassLoader)
前面兩次粗略的閱讀,能理解內(nèi)容,但是很難記住細(xì)節(jié)。每每碰到不會(huì)的知識點(diǎn)就上網(wǎng)查,所以知識點(diǎn)太碎片腦子里沒有體系不僅更不容易記住,而且更加容易混亂。但是通過這種方式記錄發(fā)現(xiàn)自己清晰了很多,就算以后忘記,知識再次撿起的成本也低了很多。
這次還有一些章節(jié)雖然閱讀了,但是還未完成記錄。等自己理解深刻有空閑了就再次記錄下來,這里的內(nèi)容均出自周志明老師的《深入理解 Java 虛擬機(jī)》,有興趣的可以入手紙質(zhì)版。
關(guān)鍵詞:虛擬
客戶&案例
營銷資訊
關(guān)于我們
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。