國內(nèi)JVM相關(guān)書籍NO.1,Java程序員必讀。讀書筆記第七部分對應(yīng)原書的第十二章和第十三章,主要介紹Java內(nèi)存模型、先行發(fā)生原則、線程安全和虛擬機(jī)的鎖優(yōu)化細(xì)節(jié)。
第五部分 高效并發(fā)
第十二章 Java內(nèi)存模型與線程
并發(fā)處理的廣泛應(yīng)用是使得Amdahl定律代替摩爾定律成為計算機(jī)性能發(fā)展源動力的根本原因,也是人類“壓榨”計算機(jī)運算能力的最有力武器。
12.1 概述
- 多任務(wù)處理在現(xiàn)代計算機(jī)操作系統(tǒng)中幾乎已是一項必備的功能了;
- 除了充分利用計算機(jī)處理器的能力外,一個服務(wù)端同時對多個客戶端提供服務(wù)則是另一個更具體的并發(fā)應(yīng)用場景;
- 服務(wù)端是Java語言最擅長的領(lǐng)域之一,不過如何寫好并發(fā)應(yīng)用程序卻又是服務(wù)端程序開發(fā)的難點之一,處理好并發(fā)方面的問題通常需要更多的編碼經(jīng)驗來支持,幸好Java語言和虛擬機(jī)提供了許多工具,把并發(fā)編碼的門檻降低了不少;
12.2 硬件的效率與一致性
- 絕大多數(shù)的運算任務(wù)不可能只靠處理器計算就能完成,處理器至少要與內(nèi)存交互,所以現(xiàn)代計算機(jī)系統(tǒng)都不得不加入一層讀寫速度盡可能接近處理器運算速度的高速緩存來作為內(nèi)存與處理器之間的緩沖:將運算需要使用到的數(shù)據(jù)復(fù)制到緩存中,讓運算能快速運行,當(dāng)運算結(jié)束后再從緩存同步回內(nèi)存之中,這樣處理器就無須等待緩慢的內(nèi)存讀寫了;
- 基于高速緩存的存儲交互很好地解決了處理器與內(nèi)存的速度矛盾,但是也為計算機(jī)系統(tǒng)帶來更高的復(fù)雜度,因為它引入了一個新的問題:緩存一致性;為了解決一致性的問題,需要各個處理器訪問緩存時都遵循一些協(xié)議,在讀寫時要根據(jù)協(xié)議來進(jìn)行操作,這類協(xié)議有MSI、MESI、MOSI、Synapse、Firefly及Dragon Protocol等;
- 本章將會多次提到內(nèi)存模型一詞,可以理解在特定的操作協(xié)議下,對特定的內(nèi)存或高速緩存進(jìn)行讀寫訪問的過程抽象;不同架構(gòu)的物理機(jī)器可以擁有不一樣的內(nèi)存模型,而Java虛擬機(jī)也有自己的內(nèi)存模型,并且這里介紹的內(nèi)存訪問操作與硬件的緩存訪問具有很高的可比性;
- 除了增加高速緩存之外,為了使得處理器內(nèi)部的運算單元能盡量被充分利用,處理器可能會對輸入代碼進(jìn)行亂序執(zhí)行優(yōu)化,處理器會在計算之后將亂序執(zhí)行的結(jié)果重組,保證該結(jié)果與順序執(zhí)行的結(jié)果是一致的;
12.3 Java內(nèi)存模型
Java虛擬機(jī)規(guī)范中視圖定義一種Java內(nèi)存模型(JMM)來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓Java程序在各種平臺下都能達(dá)到一致的內(nèi)存訪問效果。
12.3.1 主內(nèi)存與工作內(nèi)存
- Java內(nèi)存模型的主要目標(biāo)是定義程序中各個變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié);此處的變量與Java編程中所說的變量有所區(qū)別,它包括了實例字段、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但不包括局部變量與方法參數(shù),因為后者是線程私有的,不會被共享;
- Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存中,每個線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量;
- 這里所講的主內(nèi)存、工作內(nèi)存與第二章所講的Java內(nèi)存區(qū)域中的Java堆、棧、方法區(qū)等并不是同一個層次的內(nèi)存劃分,這兩者基本上是沒有關(guān)系的;線程、主內(nèi)存和工作內(nèi)存的關(guān)系如下所示:
12.3.2 內(nèi)存間交互操作
關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存之類的實現(xiàn)細(xì)節(jié),Java內(nèi)存模型中定義了以下八種操作來完成,虛擬機(jī)實現(xiàn)時必須保證下面提及的每一種操作都是原子的、不可再分的(對于double和long類型的變量的某些操作在某些平臺允許有例外):
- lock
- unlock
- read
- load
- use
- assign
- store
- write
基于理解難度和嚴(yán)謹(jǐn)性考慮,最新的JSR-133文檔中,已經(jīng)放棄采用這八種操作去定義Java內(nèi)存模型的訪問協(xié)議了,后面將會介紹一個等效判斷原則 -- 先行發(fā)生原則,用來確定一個訪問在并發(fā)環(huán)境下是否安全;
12.3.3 對于volatile型變量的特殊規(guī)則
- 關(guān)鍵字volatile可以說是Java虛擬機(jī)提供的最輕量級的同步機(jī)制;
- 當(dāng)一個變量定義為volatile之后,它將具備兩種特性:第一是保證此變量對所有線程的可見性,這里的可見性是指當(dāng)一個線程修改了這個變量的值,新的值對于其他線程來說是可以立即得知的,而普通的變量的值在線程間傳遞均需要通過主內(nèi)存來完成;另外一個是禁止指令重排序優(yōu)化,普通的變量僅僅會保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致;
- volatile變量在各個線程的工作內(nèi)存中不存在一致性問題,但是Java里面的運算并非原子操作,導(dǎo)致volatile變量的運算在并發(fā)下一樣是不安全的;
- 在不符合以下兩條規(guī)則的運算場景中,我們?nèi)匀灰ㄟ^加鎖來保證原子性:運算結(jié)果并不依賴變量的當(dāng)前值或者能夠確保只有單一的線程修改變量的值、變量不需要與其他的狀態(tài)變量共同參與不變約束;
- volatile變量讀操作的性能消耗與普通變量幾乎沒有任何差別,但是寫操作則可能會慢一些;不過大多數(shù)場景下volatile的總開銷仍然要比鎖低,我們在volatile與鎖之中選擇的唯一依據(jù)僅僅是volatile的語義能否滿足使用場景的需求;
12.3.4 對于long和double型變量的特殊規(guī)則
- 允許虛擬機(jī)將沒有被volatile修飾的64位數(shù)據(jù)的讀寫操作劃分為兩次32位的操作來進(jìn)行,即允許虛擬機(jī)實現(xiàn)選擇可以不保證64位數(shù)據(jù)類型的load、store、read和write這4個操作的原子性,這點就是所謂的long和double的非原子性協(xié)定;
- 但允許虛擬機(jī)選擇把這些操作實現(xiàn)為具有原子性的操作,目前各種平臺下的商用虛擬機(jī)幾乎都選擇把64位數(shù)據(jù)的讀寫操作作為原子操作來對待;
12.3.5 原子性、可見性與有序性
- 原子性(Atomicity):由Java內(nèi)存模型來直接保證的原子性變量操作包括read、load、assign、use、store和write;在synchronized塊之間的操作也具備原子性;
- 可見性(Visibility):是指當(dāng)一個線程修改了共享變量的值,其他線程能夠立即得知這個修改;除了volatile之外,Java還有synchronized和final關(guān)鍵字能實現(xiàn)可見性;
- 有序性(Ordering):如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個線程中觀察另一個線程,所有的操作都是無序的;Java語言提供了volatile和synchronized兩個關(guān)鍵字來保證線程之間操作的有序性;
12.3.6 先行發(fā)生原則
- 先行發(fā)生是Java內(nèi)存模型中定義的兩項操作之間的偏序關(guān)系,如果說操作A先行發(fā)生于操作B,其實就是說在發(fā)生操作B之前,操作A產(chǎn)生的影響能被操作B觀察到,影響包括了修改了內(nèi)存中共享變量的值、發(fā)送了消息、調(diào)用了方法等;
- 下面是Java內(nèi)存模型下一些天然的先行發(fā)生關(guān)系:程序次序規(guī)則、管程鎖定規(guī)則、volatile變量規(guī)則、線程啟動規(guī)則、線程終止規(guī)則、線程中斷規(guī)則、對象終結(jié)規(guī)則、傳遞性;
- 時間先后順序與先行發(fā)生原則之間基本沒有太大的關(guān)系,所以我們衡量并發(fā)安全問題的時候不要受到時間順序的干擾,一切必須以先行發(fā)生原則為準(zhǔn);
12.4 Java與線程
12.4.1 線程的實現(xiàn)
- 線程是比進(jìn)程更輕量級的調(diào)度執(zhí)行單位,線程的引入可以把一個進(jìn)程的資源分配和執(zhí)行調(diào)度分開,各個線程既可以共享進(jìn)程資源又可以獨立調(diào)度;
- Thread類與大部分的Java API有顯著的差別,它的所有關(guān)鍵方法都是聲明為Native的;
- 實現(xiàn)線程主要有三種方式:使用內(nèi)核線程實現(xiàn)(系統(tǒng)調(diào)用代價相對較高、一個系統(tǒng)支持輕量級進(jìn)程的數(shù)量是有限的)、使用用戶線程實現(xiàn)(優(yōu)勢在于不需要系統(tǒng)內(nèi)核支援,劣勢在于所有線程操作都需要用戶程序自己處理)和使用用戶線程加輕量級進(jìn)程混合實現(xiàn)(用戶線程是完全建立在用戶空間中,因此用戶線程的創(chuàng)建、切換等操作依然廉價,并且可以支持大規(guī)模的用戶線程并發(fā);而操作系統(tǒng)提供支持的輕量級進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁,這樣可以使用內(nèi)核提供的線程調(diào)度功能及處理器映射,并且用戶線程的系統(tǒng)調(diào)用要通過輕量級線程來完成,大大降低了整個進(jìn)程被完全阻塞的風(fēng)險);
- 對于Sun JDK來說,它的Windows版與Linux版都是使用一對一的線程模型實現(xiàn)的,一條Java線程就映射到一條輕量級進(jìn)程之中,因為Windows和Linux系統(tǒng)提供的線程模式就是一對一的;
12.4.2 Java線程調(diào)度
- 線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過程,主要調(diào)度方式有兩種,分別是協(xié)同式線程調(diào)度(線程的執(zhí)行時間由線程本身來控制)和搶占式線程調(diào)度(線程由系統(tǒng)來分配執(zhí)行時間,線程的切換不由線程本身來決定);
- Java語言一共設(shè)置了10個級別的線程優(yōu)先級,不過線程優(yōu)先級并不是太靠譜,原因就是操作系統(tǒng)的線程優(yōu)先級不見得總是與Java線程的優(yōu)先級一一對應(yīng),另外優(yōu)先級還可能被系統(tǒng)自行改變;
12.4.3 狀態(tài)轉(zhuǎn)換
- Java語言定義了五種線程狀態(tài),在任意一個時間點,一個線程只能有且只有其中一種狀態(tài),分別是新建(New)、運行(Runnable)、無限期等待(Waiting)、限期等待(Timed Waiting)、阻塞(Blocled)、結(jié)束(Terminated)。它們之間相互的轉(zhuǎn)換關(guān)系如下所示:
12.5 本章小結(jié)
本章我們首先了解了虛擬機(jī)Java內(nèi)存模型的結(jié)構(gòu)及操作,然后講解了原子性、可見性、有序性在Java內(nèi)存模型中的體現(xiàn),最后介紹了先行發(fā)生原則的規(guī)則及使用。另外,我們還了解了線程在Java語言之中是如何實現(xiàn)的。
在本章主要介紹了虛擬機(jī)如何實現(xiàn)并發(fā),而在下一章我們主要關(guān)注點將是虛擬機(jī)如何實現(xiàn)高效,以及虛擬機(jī)對我們編寫的并發(fā)代碼提供了什么樣的優(yōu)化手段。
第十三章 線程安全與鎖優(yōu)化
13.1 概述
- 首先需要保證并發(fā)的正確性,然后在此基礎(chǔ)上實現(xiàn)高效;
13.2 線程安全
Brian Goetz對線程安全有一個比較恰當(dāng)?shù)亩x:當(dāng)多個線程訪問一個對象時,如果不用考慮這些線程在運行時環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作,調(diào)用這個對象的行為都可以獲得正確的結(jié)果,那這個對象是線程安全的。
13.2.1 Java語言中的線程安全
- 我們可以將Java語言中各個操作共享的數(shù)據(jù)分為以下五類:不可變、絕對線程安全、相對線程安全、線程兼容和線程對立;
- 不可變:不可變帶來的安全性是最簡單和最純粹的,如final的基本數(shù)據(jù)類型;如果共享的數(shù)據(jù)是一個對象,那就需要保證對象的行為不會對其狀態(tài)產(chǎn)生任何影響才行,比如String類的substring、replace方法;Number類型的大部分子類都符合不可變要求的類型,但是AtomicInteger和AtomicLong則并非不可變的;
- 線程絕對安全:Java API中標(biāo)注自己是線程安全的類,大多數(shù)都不是絕對的線程安全;比如java.util.Vector,不意味著調(diào)用它的是時候永遠(yuǎn)都不再需要同步手段了;
- 線程相對安全:是我們通常意義上所講的線程安全,在Java語言中,大部分的線程安全類都屬于這種類型;
- 線程兼容:指對象本身并不是線程安全的,但是可以通過在調(diào)用端正確地使用同步手段來保證對象在并發(fā)環(huán)境中可以安全地使用;我們說一個類不是線程安全的,絕大多數(shù)時候指的是這一種情況;
- 線程對立:無論調(diào)用端是否采取了同步措施,都無法在多線程環(huán)境中并發(fā)使用的代碼,Java語言中很少出現(xiàn);
13.2.2 線程安全的實現(xiàn)方法
- 互斥同步:同步是指在多個線程并發(fā)訪問共享數(shù)據(jù)時,保證共享數(shù)據(jù)在同一個時刻只被一個線程使用,而互斥是實現(xiàn)同步的一種手段,臨界區(qū)、互斥量和信號量都是主要的互斥實現(xiàn)方式;Java中最基本的互斥同步手段就是synchronized關(guān)鍵字,它對同一個線程來說是可重入的且會阻塞后面其他線程的進(jìn)入;另外還可以使用java.util.concurrent包中的重入鎖(ReentrantLock)來實現(xiàn)同步,相比synchronized關(guān)鍵字ReentrantLock增加了一些高級功能:等待可中斷、可實現(xiàn)公平鎖以及鎖可以綁定多個條件;
- 非阻塞同步:互斥同步最主要的問題就是進(jìn)行線程阻塞和喚醒帶來的性能問題,其屬于一種悲觀的并發(fā)策略;隨著硬件指令集的發(fā)展,我們有了另外一個選擇即基于沖突檢測的樂觀并發(fā)策略,就是先進(jìn)行操作,如果沒有其他線程爭用共享數(shù)據(jù)那就操作成功了,如果有爭用產(chǎn)生了沖突,那就再采取其他的補(bǔ)償措施(最常見的就是不斷重試直至成功),這種同步操作稱為非阻塞同步;Java并發(fā)包的整數(shù)原子類,其中的compareAndSet和getAndIncrement等方法都使用了Unsafe類的CAS操作;
- 無同步方案:要保證線程安全,并不是一定就要進(jìn)行同步;有一些代碼天生就是線程安全的,比如可重入代碼和線程本地存儲的代碼;
13.3 鎖優(yōu)化
13.3.1 自旋鎖與自適應(yīng)自旋
- 互斥同步對性能最大的影響是阻塞的實現(xiàn),掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性能帶來了很大的壓力;另外在共享數(shù)據(jù)的鎖定狀態(tài)只會持續(xù)很短的一段時間,為了這段時間去掛起和恢復(fù)線程并不值得,如果讓兩個或以上的線程同時并行執(zhí)行,讓后面請求鎖的那個線程稍等一下,但不放棄處理器的執(zhí)行時間,看看持有鎖的線程是否很快就會釋放鎖;為了讓線程等待,我們只需讓線程執(zhí)行一個忙循環(huán),這些技術(shù)就是所謂的自旋鎖;
- 在JDK 1.6已經(jīng)默認(rèn)開啟自旋鎖;如果鎖被占用的時間很短自旋等待的效果就會非常好,反之則會白白消耗處理器資源;
- 在JDK 1.6中引入了自適應(yīng)的自旋鎖,這意味著自旋的時間不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定;
13.3.2 鎖消除
- 鎖消除是指虛擬機(jī)即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進(jìn)行消除;
- 鎖消除的主要判斷依據(jù)來源于逃逸分析的數(shù)據(jù)支持;
13.3.3 鎖粗化
- 原則上總是推薦將同步塊的作用范圍限制得盡量小 -- 只有在共享數(shù)據(jù)的實際作用域中才進(jìn)行同步,這樣是為了使得需要同步的操作數(shù)量盡可能變小,如果存在鎖競爭,那等待鎖的線程也能盡快拿到鎖;
- 但是如果一系列的連續(xù)操作都對同一個對象反復(fù)加鎖和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,那即使沒有線程競爭,頻繁地進(jìn)行互斥同步操作也會導(dǎo)致不必要的性能損耗;
13.3.4 輕量級鎖
- 輕量級鎖是JDK 1.6之中加入的新型鎖機(jī)制,它是相對于使用操作系統(tǒng)互斥量來實現(xiàn)的傳統(tǒng)鎖而言的;它并不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統(tǒng)的重量級鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗;
- 要理解輕量級鎖,以及后面會講到的偏向鎖的原理和運作過程,必須從HotSpot虛擬機(jī)的對象的內(nèi)存布局開始介紹;HotSpot虛擬機(jī)的對象頭分為兩部分信息:第一部分用于存儲對象自身的運行時數(shù)據(jù),如哈希碼、GC分代年齡等,這部分官方稱之為Mark Word,是實現(xiàn)輕量級鎖和偏向鎖的關(guān)鍵,另外一部分用于存儲指向方法區(qū)對象類型數(shù)據(jù)的指針; Mark Word被設(shè)計成一個非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間存儲盡量多的信息,在32位的HotSpot虛擬機(jī)中對象未被鎖定的狀態(tài)下,25bit用于存儲對象哈希碼,4bit用于存儲對象分代年齡,2bit用于存儲鎖標(biāo)志位,1bit固定為0;在其他狀態(tài)(輕量級鎖定、重量級鎖定、GC標(biāo)志、可偏向)下對象的存儲內(nèi)容如下:
- 在代碼進(jìn)入同步塊的時候,如果此同步對象沒有被鎖定,虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲對象目前的Mark Word的拷貝(官方稱之為Displaced Mark Word);然后虛擬機(jī)將使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針,如果更新成功了那么這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖標(biāo)志位將轉(zhuǎn)變?yōu)椤?0”,即表示此對象處于輕量級鎖定狀態(tài);如果這個更新操作失敗了,虛擬機(jī)首先會檢查對象的Mark Word是否指向當(dāng)前線程的棧幀,如果是就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行,否則說明這個鎖對象已經(jīng)被其他線程搶占了;如果有兩條以上的線程爭用同一個鎖,那輕量級鎖就不再有效,要膨脹為重量級鎖,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”,Mark Word中存儲的就是指向重量級鎖的指針,后面等待鎖的線程也要進(jìn)行阻塞狀態(tài);
- 輕量級鎖能提升程序同步性能的依據(jù)是“對于絕大部分的鎖,在整個同步周期內(nèi)都是不存在競爭的”,這是一個經(jīng)驗數(shù)據(jù);
13.3.5 偏向鎖
- 偏向鎖也是JDK 1.6中引入的一項鎖優(yōu)化,它的目的是消除數(shù)據(jù)在無競爭情況下的同步原語,進(jìn)一步提高程序的運行性能;如果說輕量級鎖是在無競爭的情況下使用CAS操作去消除同步使用的互斥量,那偏向鎖就是在無競爭的情況下把整個同步都消除掉,連CAS操作都不做了;
- 偏向鎖會偏向于第一個獲得它的線程,如果在接下來的執(zhí)行過程中,該鎖沒有被其他的線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步;
- 假設(shè)當(dāng)前虛擬機(jī)啟動了偏向鎖,那么當(dāng)鎖對象第一次被線程獲取的時候,虛擬機(jī)將會把對象頭中的標(biāo)志位設(shè)為“01”,即偏向模式;同時使用CAS操作把獲取到這個鎖的線程ID記錄在對象的Mark Word之中;如果CAS操作成功,持有偏向鎖的線程以后每次進(jìn)入這個鎖相關(guān)的同步塊時,虛擬機(jī)都可以不再進(jìn)行任何同步操作;當(dāng)有另外一個線程去嘗試獲取這個鎖時,偏向模式就宣告結(jié)束,根據(jù)鎖對象目前是否被鎖定的狀態(tài),撤銷偏向后恢復(fù)到未鎖定或輕量級鎖定的狀態(tài),后續(xù)的同步操作就如上面介紹的輕量級鎖那樣執(zhí)行;偏向鎖、輕量級鎖的狀態(tài)轉(zhuǎn)化以及對象Mark Work的關(guān)系如下圖所示:
- 偏向鎖可以提高帶有同步但無競爭的程序性能,它同樣是一個帶有效益權(quán)衡性質(zhì)的優(yōu)化;
本章小結(jié)
本章介紹了線程安全所涉及的概念和分類、同步實現(xiàn)的方式及虛擬機(jī)的底層運行原理,并且介紹了虛擬機(jī)為了實現(xiàn)高效并發(fā)所采取的一系列鎖優(yōu)化措施。
系列讀書筆記
- 《深入理解Java虛擬機(jī)》讀書筆記1:Java技術(shù)體系、Java內(nèi)存區(qū)域和內(nèi)存溢出異常
- 《深入理解Java虛擬機(jī)》讀書筆記2:垃圾收集器與內(nèi)存分配策略
- 《深入理解Java虛擬機(jī)》讀書筆記3:虛擬機(jī)性能監(jiān)控與調(diào)優(yōu)實戰(zhàn)
- 《深入理解Java虛擬機(jī)》讀書筆記4:類文件結(jié)構(gòu)
- 《深入理解Java虛擬機(jī)》讀書筆記5:類加載機(jī)制與字節(jié)碼執(zhí)行引擎
- 《深入理解Java虛擬機(jī)》讀書筆記6:程序編譯與代碼優(yōu)化
- 《深入理解Java虛擬機(jī)》讀書筆記7:高效并發(fā)