問(wèn)題集錦-JVM與多線程

JVM-Class類文件結(jié)構(gòu)

常量池:字面量(字符串和final常量)和符號(hào)引用(類和接口的全限定名、字段的名稱和描述符、方法句柄和方法類型、方法的名稱和描述符 )
字段表、方法表、屬性表(code屬性存放代碼)

JVM-運(yùn)行時(shí)數(shù)據(jù)區(qū)域

方法區(qū)(線程共享,可能會(huì)內(nèi)存溢出):用于存儲(chǔ)已被虛擬機(jī)加載的類型信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)
1、以前使用永久代來(lái)實(shí)現(xiàn)方法區(qū)導(dǎo)致Java應(yīng)用更容易遇到內(nèi)存溢出的問(wèn)題,現(xiàn)在使用本地內(nèi)存來(lái)實(shí)現(xiàn)方法區(qū)
2、JDK 7的HotSpot,已經(jīng)把原本放在永久代的字符串常量池、靜態(tài)變量等移出(移到堆內(nèi))
3、在JDK 8,完全廢棄了永久代,使用本地內(nèi)存中實(shí)現(xiàn)的元空間來(lái)代替,把JDK 7中永久代還剩余的內(nèi)容(主要是類型信息)全部移到元空間中
4、運(yùn)行時(shí)常量池:Class文件中常量池在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中(也存放直接引用)
堆(線程共享,可能會(huì)內(nèi)存溢出):
1、存放對(duì)象實(shí)例, “幾乎”所有的對(duì)象實(shí)例都在這里分配內(nèi)存(由于即時(shí)編譯技術(shù)的進(jìn)步,尤其是逃逸分析技術(shù)的日漸強(qiáng)大,棧上分配、標(biāo)量替換優(yōu)化手段已經(jīng)導(dǎo)致對(duì)象可以不在堆內(nèi)分配)
2、堆中可以劃分出多個(gè)線程私有的分配緩沖區(qū) ,以更好地回收內(nèi)存,或者更快地分配內(nèi)存
3、通過(guò)參數(shù)-Xmx和-Xms設(shè)定堆內(nèi)存大小
直接內(nèi)存(可能會(huì)內(nèi)存溢出):
1、直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java中的內(nèi)存區(qū)域
2、應(yīng)用:NIO是一種基于通道(Channel)與緩沖區(qū) (Buffer)的I/O方式,它可以使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆里面的 DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作,這樣能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬嗽贘ava堆和Native堆中來(lái)回復(fù)制數(shù)據(jù)(零拷貝)
棧(線程私有,可能會(huì)內(nèi)存溢出和堆棧溢出):
1、每個(gè)方法被執(zhí)行的時(shí)候,Java虛擬機(jī)都會(huì)同步創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)連接、方法返回地址等信息
2、每一個(gè)方法被調(diào)用直至執(zhí)行完畢的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程
局部變量表(棧幀中,大小在編譯期已經(jīng)確定,code屬性中存儲(chǔ)大?。?br> 1、用于存放方法參數(shù)和方法內(nèi)部定義的局部變量(基本類型和對(duì)象引用)、returnAddress 類型(指向了一條字節(jié)碼指令的地址),這些數(shù)據(jù)類型在局部變量表中的存儲(chǔ)空間以局部變量槽來(lái)表示
2、當(dāng)一個(gè)方法被調(diào)用時(shí),使用局部變量表來(lái)完成實(shí)參到形參的傳遞
3、如果執(zhí)行的是實(shí)例方法(沒(méi)有被static修飾的方法),局部變量表中第0位存放this引用,在方法中直接訪問(wèn)
4、局部變量表中的變量槽是可以重用的;復(fù)用的時(shí)機(jī)是其他變量需要用到這塊變量槽,因此即使該變量已經(jīng)不被使用,也不會(huì)垃圾回收,除非賦值null(不建議,會(huì)被優(yōu)化掉)
5、局部變量不像類變量那樣存在“準(zhǔn)備階段”,類變量有兩次賦初始值的過(guò)程,一次在準(zhǔn)備階段,賦予系統(tǒng)初始值;另外一次在初始化階段,賦予代碼定義的初始值,因此即使在初始化階段代碼沒(méi)有為類變量賦值也沒(méi)有關(guān)系,類變量仍然具有一個(gè)確定的初始值,不會(huì)產(chǎn)生歧義;但局部變量就不一樣了,如果一個(gè)局部變量定義了但沒(méi)有賦初始值,那它是完全不能使用的
操作數(shù)棧(棧幀中,大小在編譯期已經(jīng)確定,code屬性中存儲(chǔ)大?。?br> 1、兩個(gè)棧幀會(huì)出現(xiàn)一部分重疊,讓下面棧幀的部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起,這樣做不僅節(jié)約了一些空間,更重要的是在進(jìn)行方法調(diào)用時(shí)就可以直接共用一部分?jǐn)?shù)據(jù),無(wú)須進(jìn)行額外的參數(shù)復(fù)制傳遞
動(dòng)態(tài)連接(棧幀中):
1、每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接
2、Class文件的常量池中存有大量的符號(hào)引用,字節(jié)碼中的方法調(diào)用指令就以常量池里指向方法的符號(hào)引用作為參數(shù),這些符號(hào)引用一部分會(huì)在類加載階段或者第一次使用的時(shí)候就被轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化被稱為靜態(tài)解析;另外一部分將在每一次運(yùn)行期間都轉(zhuǎn)化為直接引用,這部分就稱為動(dòng)態(tài)連接
方法返回地址(棧幀中):方法退出時(shí)使用
程序計(jì)數(shù)器(線程私有,不會(huì)內(nèi)存溢出):字節(jié)碼解釋器工作時(shí)通過(guò)改變計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令(線程切換時(shí)為了標(biāo)識(shí)每個(gè)線程執(zhí)行位置)
執(zhí)行引擎:
1、在不同的虛擬機(jī)實(shí)現(xiàn)中,執(zhí)行引擎在執(zhí)行字節(jié)碼的時(shí)候,通常會(huì)有解釋執(zhí)行(通過(guò)解釋器執(zhí)行)和編譯執(zhí)行(通過(guò)即時(shí)編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇,也可能兩者兼?zhèn)?,還可能會(huì)有同時(shí)包含幾個(gè)不同級(jí)別的即時(shí)編譯器一起工作的執(zhí)行引擎
2、從外觀上來(lái)看,所有的Java虛擬機(jī)的執(zhí)行引擎輸入、輸出都是一致的:輸入的是字節(jié)碼二進(jìn)制流,處理過(guò)程是字節(jié)碼解析執(zhí)行的等效過(guò)程,輸出的是執(zhí)行結(jié)果
方法調(diào)用:
1、方法調(diào)用并不等同于方法中的代碼被執(zhí)行,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本 (即調(diào)用哪一個(gè)方法),暫時(shí)還未涉及方法內(nèi)部的具體運(yùn)行過(guò)程
2、Class文件的編譯過(guò)程中不包含傳統(tǒng)程序語(yǔ)言編譯的連接步驟,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用,而不是直接引用
3、解析:所有方法調(diào)用的目標(biāo)方法在Class文件里面都是一個(gè)常量池中的符號(hào)引用,在類加載的解析階段,會(huì)將其中的一部分符號(hào)引用轉(zhuǎn)化為直接引用,即調(diào)用目標(biāo)在程序代碼寫好、編譯器進(jìn)行編譯那一刻就已經(jīng)確定下來(lái),這類方法的調(diào)用被稱為解析
(靜態(tài)方法、私有方法、final方法、構(gòu)造方法、父類方法)
4、分派
**靜態(tài)分派:重載
引用類型和實(shí)際類型在程序中都可能會(huì)發(fā)生變化,區(qū)別是引用類型的變化僅僅在使用時(shí)發(fā)生,引用類型不會(huì)被改變,并且最終的引用類型是在編譯期可知的;而實(shí)際類型變化的結(jié)果在運(yùn)行期才可確定,編譯器在編譯程序的時(shí)候并不知道一個(gè)對(duì)象的實(shí)際類型是什么

// 實(shí)際類型變化 
Human human = (new Random()).nextBoolean() ? new Man() : new Woman(); 
// 引用類型變化 
sr.sayHello((Man) human) ;
sr.sayHello((Woman) human);

虛擬機(jī)(或者準(zhǔn)確地說(shuō)是編譯器)在重載時(shí)是通過(guò)參數(shù)的引用類型而不是實(shí)際類型作為判定依據(jù)的,由于引用類型在編譯期可知,所以在編譯階段,Javac編譯器就根據(jù)參數(shù)的引用類型決定了會(huì)使用哪個(gè)重載版本
所有依賴引用類型來(lái)決定方法執(zhí)行版本的分派動(dòng)作,都稱為靜態(tài)分派,靜態(tài)分派的最典型應(yīng)用表現(xiàn)就是方法重載,靜態(tài)分派發(fā)生在編譯階段,因此確定靜態(tài)分派的動(dòng)作實(shí)際上不是由虛擬機(jī)來(lái)執(zhí)行的
**動(dòng)態(tài)分派 覆蓋
在運(yùn)行期根據(jù)實(shí)際類型確定方法執(zhí)行版本的分派過(guò)程稱為動(dòng)態(tài)分派
**單分派和多分配派 重載+覆蓋
方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量
單分派是根據(jù)一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇,多分派則是根據(jù)多于一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇
編譯階段:根據(jù)方法接收者的引用類型+方法參數(shù)類型才能確定使用哪個(gè)類的哪個(gè)方法,因此靜態(tài)分派屬于多分派類型
運(yùn)行階段:根據(jù)方法接收者的實(shí)際類型就能確定使用哪個(gè)類(會(huì)覆蓋靜態(tài)分派的判斷,使用哪個(gè)方法是靜態(tài)分派確定),因此動(dòng)態(tài)分派屬于單分派類型

JVM-類加載機(jī)制

定義:Java虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型的過(guò)程
在Java語(yǔ)言里面,類型的加載、連接和初始化過(guò)程都是在程序運(yùn)行期間完成的
類的生命周期:加載 、連接(驗(yàn)證、準(zhǔn)備、解析)、初始化 、使用和卸載
類加載的時(shí)機(jī)
對(duì)于初始化階段,有且只有6種情況必須對(duì)類進(jìn)行初始化(主動(dòng)引用),其余都是被動(dòng)引用:
1、new對(duì)象、讀寫類型靜態(tài)字段(final常量除外,編譯期已放入常量池)、調(diào)用類型靜態(tài)方法時(shí)
2、對(duì)類型反射調(diào)用時(shí)
3、初始化子類時(shí),要先初始化父類
4、虛擬機(jī)啟動(dòng)時(shí),初始化主類
5、方法句柄對(duì)應(yīng)的類初始化
6、接口定義了默認(rèn)方法,實(shí)現(xiàn)類初始化時(shí),要先初始化接口
被動(dòng)引用:
1、通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化(對(duì)于靜態(tài)字段, 只有直接定義這個(gè)字段的類才會(huì)被初始化)
2、通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化(與數(shù)組是不同的類)
3、常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上沒(méi)有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化(在編譯階段通過(guò)常量傳播優(yōu)化,已經(jīng)將常量的值直接存儲(chǔ)在調(diào)用類的常量池中,以后調(diào)用類對(duì)常量的引用,實(shí)際都被轉(zhuǎn)化為對(duì)自身常量池的引用了;也就是說(shuō),實(shí)際上調(diào)用類的Class文件之中并沒(méi)有原類的符號(hào)引用入口,這兩個(gè)類在編譯成 Class文件后就已經(jīng)不存在任何聯(lián)系了)
類加載的過(guò)程
加載:
1、通過(guò)類的全限定名獲取定義類的二進(jìn)制字節(jié)流(非數(shù)組類型的加載既可以使用引導(dǎo)類加載器來(lái)完成,也可以由用戶自定義的類加載器去完成(重寫一個(gè)類加載器的findClass或loadClass方法);數(shù)組類本身不通過(guò)類加載器創(chuàng)建,是由Java虛擬機(jī)直接在內(nèi)存中動(dòng)態(tài)構(gòu)造出來(lái)的,但數(shù)組類與類加載器仍然有很密切的關(guān)系,因?yàn)閿?shù)組類的元素類型最終還是要靠類加載器來(lái)完成加載)
2、將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
3、在內(nèi)存中生成代表類的Class對(duì)象,作為方法區(qū)中類的各種數(shù)據(jù)的訪問(wèn)入口
驗(yàn)證:
1、文件格式驗(yàn)證
2、元數(shù)據(jù)驗(yàn)證(是否有父類、是否繼承了不該繼承的如final類、是否實(shí)現(xiàn)了抽象類的所有方法等)
3、字節(jié)碼驗(yàn)證:對(duì)類的方法體(Class文件中的code屬性)進(jìn)行檢驗(yàn)分析
4、符號(hào)引用驗(yàn)證:將符號(hào)引用轉(zhuǎn)換為直接引用時(shí)驗(yàn)證(解析階段),通過(guò)全限定名是否能找到對(duì)應(yīng)的類等
準(zhǔn)備:
1、準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置初始值(默認(rèn)值)的階段,這些類變量所使用的內(nèi)存都在方法區(qū)(jdk1.8在堆中)中進(jìn)行分配
2、實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中
3、類常量在這個(gè)階段會(huì)被初始化(不是默認(rèn)值)
解析:將常量池內(nèi)的符號(hào)引用替換為直接引用
1、符號(hào)引用:符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定是已經(jīng)加載到虛擬機(jī)內(nèi)存當(dāng)中的內(nèi)容
2、直接引用:直接引用是可以直接指向目標(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)存中存在
初始化:
1、初始化階段就是執(zhí)行類構(gòu)造器方法(所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{}塊)中的語(yǔ)句,執(zhí)行順序是代碼中的順序)的過(guò)程,靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語(yǔ)句塊可以賦值,但是不能訪問(wèn)
2、父類的類構(gòu)造器方法先執(zhí)行,父類中定義的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的變量賦值操作
3、Java虛擬機(jī)必須保證一個(gè)類的類構(gòu)造器方法在多線程環(huán)境中被正確地加鎖同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有其中一個(gè)線程去執(zhí)行這個(gè)類的類構(gòu)造器方法,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行完畢類構(gòu)造器方法,如果在一個(gè)類的類構(gòu)造器方法中有耗時(shí)很長(zhǎng)的操作,那就可能造成多個(gè)進(jìn)程阻塞,在實(shí)際應(yīng)用中這種阻塞往往是很隱蔽的
4、同一個(gè)類加載器下,一個(gè)類型只會(huì)被初始化一次
類加載器
1、類加載器通過(guò)一個(gè)類的全限定名來(lái)獲取描述該類的二進(jìn)制字節(jié)流
2、對(duì)于任意一個(gè)類,都必須由加載它的類加載器和這個(gè)類本身一起共同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間,即比較兩個(gè)類是否“相等”,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義,否則,即使這兩個(gè)類來(lái)源于同一個(gè) Class文件,被同一個(gè)Java虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等
雙親委派模型
1、Java一直保持著三層類加載器、雙親委派的類加載架構(gòu)
2、三層類加載器:
**啟動(dòng)類加載器:這個(gè)類加載器負(fù)責(zé)加載存放在 <JAVA_HOME>\lib目錄中的類,啟動(dòng)類加載器無(wú)法被Java程序直接引用,用戶在編寫自定義類加載器時(shí), 如果需要把加載請(qǐng)求委派給啟動(dòng)類加載器去處理,那直接使用null代替即可
**擴(kuò)展類加載器:這個(gè)類加載器負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的類,由于擴(kuò)展類加載器是由Java代碼實(shí)現(xiàn)的,開(kāi)發(fā)時(shí)可以直接在程序中使用擴(kuò)展類加載器來(lái)加載Class文件
**應(yīng)用程序類加載器:這個(gè)類加載器負(fù)責(zé)加載用戶類路徑(ClassPath)上所有的類庫(kù),開(kāi)發(fā)時(shí)同樣可以直接在代碼中使用這個(gè)類加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器
3、各種類加載器之間的層次關(guān)系被稱為類加載器的雙親委派模型,雙親委派模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)有自己的父類加載器,不過(guò)這里類加載器之間的父子關(guān)系一般不是以繼承的關(guān)系來(lái)實(shí)現(xiàn)的,而是通常使用組合關(guān)系來(lái)復(fù)用父類加載器的代碼(高內(nèi)聚,低耦合)
4、雙親委派模型的工作流程:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到最頂層的啟動(dòng)類加載器中,只有當(dāng)父類加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒(méi)有找到所需的類)時(shí),子類加載器才會(huì)嘗試自己去完成加載
5、使用雙親委派模型來(lái)組織類加載器之間的關(guān)系,一個(gè)顯而易見(jiàn)的好處就是Java中的類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系,如類java.lang.Object,無(wú)論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都能夠保證是同一個(gè)類,反之,如果沒(méi)有使用雙親委派模型,都由各個(gè)類加載器自行去加載的話,如果用戶自己也編寫了一個(gè)名為java.lang.Object的類,并放在程序的 ClassPath中,那系統(tǒng)中就會(huì)出現(xiàn)多個(gè)不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無(wú)從保證,應(yīng)用程序?qū)?huì)變得一片混亂,如果寫一個(gè)與rt.jar類庫(kù)中已有類重名的Java 類,將會(huì)發(fā)現(xiàn)它可以正常編譯,但永遠(yuǎn)無(wú)法被加載運(yùn)行
6、雙親委派模型實(shí)現(xiàn):先檢查請(qǐng)求加載的類型是否已經(jīng)被加載過(guò),若沒(méi)有則調(diào)用父加載器的 loadClass()方法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器,如果父類加載器加載失敗, 拋出異常,才調(diào)用自己的findClass()方法嘗試進(jìn)行加載
破壞雙親委派模型
1、按照l(shuí)oadClass()方法的邏輯,如果父類加載失敗,會(huì)自動(dòng)調(diào)用自己的findClass()方法來(lái)完成加載,這樣既不影響用戶按照自己的意愿去加載類,又可以保證新寫出來(lái)的類加載器是符合雙親委派規(guī)則的
2、雙親委派很好地解決了各個(gè)類加載器協(xié)作時(shí)基礎(chǔ)類型的一致性問(wèn)題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類型之所以被稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a繼承、調(diào)用的API存在,但程序設(shè)計(jì)往往沒(méi)有絕對(duì)不變的完美規(guī)則,如果有基礎(chǔ)類型又要調(diào)用回用戶的代碼,那該怎么辦呢?如JDBC使用線程上下文類加載器去加載所需的代碼,這是一種父類加載器去請(qǐng)求子類加載器完成類加載的行為,這種行為實(shí)際上是打通了雙親委派模型的層次結(jié)構(gòu)來(lái)逆向使用類加載器,已經(jīng)違背了雙親委派模型的一般性原則,但也是無(wú)可奈何的事情
3、由于用戶對(duì)程序動(dòng)態(tài)性(如代碼熱替換、模塊熱部署)的追求,需要破壞雙親委派模型;OSGi實(shí)現(xiàn)模塊化熱部署的關(guān)鍵是它自定義的類加載器機(jī)制的實(shí)現(xiàn),每一個(gè)程序模塊都有一個(gè)自己的類加載器,當(dāng)需要更換一個(gè)程序模塊,就把程序模塊連同類加載器一起換掉以實(shí)現(xiàn)代碼的熱替換

JVM-對(duì)象

對(duì)象的創(chuàng)建
1、校驗(yàn)是否能在常量池中定位到一個(gè)類的符號(hào)引用
2、校驗(yàn)符號(hào)引用代表的類是否已被加載、解析和初始化過(guò),如果沒(méi)有,必須先執(zhí)行相應(yīng)的類加載過(guò)程
3、為新生對(duì)象分配內(nèi)存,對(duì)象所需內(nèi)存的大小在類加載完成后便可完全確定,使用指針碰撞(連續(xù)分配)或空閑列表(記錄可用空閑內(nèi)存)來(lái)分配內(nèi)存(需要處理并發(fā)情況下線程安全問(wèn)題,對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理(CAS+失敗重試)或把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存TLAB,只有本地TLAB用完了,分配新的緩存區(qū)時(shí)才需要同步鎖定)
4、將分配到的內(nèi)存空間(但不包括對(duì)象頭)都初始化為零值,保證對(duì)象的實(shí)例變量可以不賦初始值就可以直接使用
5、設(shè)置對(duì)象頭信息,如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼(真正調(diào)用Object::hashCode方法時(shí)才計(jì)算)、對(duì)象的GC分代年齡等信息;根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同,如是否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式
6、執(zhí)行構(gòu)造函數(shù),初始化實(shí)例變量
對(duì)象的內(nèi)存結(jié)構(gòu)
對(duì)象頭、實(shí)例數(shù)據(jù)、對(duì)齊填充
1、對(duì)象頭:包括兩類信息,第一類是用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時(shí)間戳等,即Mark Word,是一個(gè)動(dòng)態(tài)定義的數(shù)據(jù)結(jié)構(gòu)


image.png

第二類是類型指針(如果是句柄就不需要),即對(duì)象指向它的類型元數(shù)據(jù)的指針,通過(guò)這個(gè)指針可以確定該對(duì)象是哪個(gè)類的實(shí)例
此外,如果對(duì)象是一個(gè)Java數(shù)組,在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)
2、實(shí)例數(shù)據(jù):無(wú)論是從父類繼承下來(lái)的,還是在子類中定義的實(shí)例變量(不包括靜態(tài)變量)都必須記錄起來(lái)
對(duì)象的訪問(wèn)定位
通過(guò)棧上的reference(句柄和直接指針)數(shù)據(jù)來(lái)操作堆上的具體對(duì)象
1、句柄(易于維護(hù)):Java堆中將可能會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自具體的地址信息
2、直接指針(速度快,HotSpot使用):在Mark Word中存放類型指針去訪問(wèn)類型數(shù)據(jù)的相關(guān)信息,reference中存儲(chǔ)的直接就是對(duì)象地址,如果只是訪問(wèn)對(duì)象本身的話,就不需要多一次間接訪問(wèn)的開(kāi)銷

JVM-垃圾收集

哪些內(nèi)存需要回收? Java堆和方法區(qū)
方法區(qū)的垃圾收集:廢棄的常量和不再使用的類型
判定一個(gè)類型是否屬于“不再被使用的類”:
1、該類所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實(shí)例
2、加載該類的類加載器已經(jīng)被回收
3、該類對(duì)應(yīng)的Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法
什么時(shí)候回收?
引用計(jì)數(shù)算法
在對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加一;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減一;任何時(shí)刻計(jì)數(shù)器為零的對(duì)象就是不可能再被使用的
很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題
可達(dá)性分析算法
通過(guò)一系列稱為“GC Roots”的根對(duì)象作為起始節(jié)點(diǎn)集,根據(jù)引用關(guān)系連接其他對(duì)象,如果某個(gè)對(duì)象到GC Roots間不可達(dá),則對(duì)象是不可能再被使用的
根對(duì)象:
1、在虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象,如參數(shù)、局部變量、臨時(shí)變量
2、在方法區(qū)中類變量引用的對(duì)象
3、在方法區(qū)中常量引用的對(duì)象
4、在本地方法棧中Native方法引用的對(duì)象
5、Java虛擬機(jī)內(nèi)部的引用,如基本數(shù)據(jù)類型對(duì)應(yīng)的Class對(duì)象,一些常駐的異常對(duì)象等,還有系統(tǒng)類加載器
6、所有被同步鎖(synchronized關(guān)鍵字)持有的對(duì)象
某個(gè)區(qū)域里的對(duì)象完全有可能被位于堆中其他區(qū)域的對(duì)象所引用,這時(shí)候就需要將這些關(guān)聯(lián)區(qū)域的對(duì)象也一并加入GC Roots集合中去,才能保證可達(dá)性分析的正確性
引用:
1、強(qiáng)引用,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象
2、軟引用,只被軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒(méi)有足夠的內(nèi)存, 才會(huì)拋出內(nèi)存溢出異常
3、弱引用,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生為止,當(dāng)垃圾收集器開(kāi)始工作,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象
4、虛引用,一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象實(shí)例
如何回收?分代收集理論
分代收集理論:
1、弱分代假說(shuō):絕大多數(shù)對(duì)象都是朝生夕滅的
2、強(qiáng)分代假說(shuō):熬過(guò)越多次垃圾收集過(guò)程的對(duì)象就越難以消亡
3、跨代引用假說(shuō):跨代引用相對(duì)于同代引用來(lái)說(shuō)僅占極少數(shù)(針對(duì)對(duì)象不是孤立的,對(duì)象之間會(huì)存在跨代引用)(在新生代上建立一個(gè)全局的數(shù)據(jù)結(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)銷,但比起收集時(shí)掃描整個(gè)老年代來(lái)說(shuō)仍然是劃算的)
新生代收集(Minor GC/Young GC):指目標(biāo)只是新生代的垃圾收集
老年代收集(Major GC/Old GC):指目標(biāo)只是老年代的垃圾收集,目前只有CMS收集器會(huì)有單獨(dú)收集老年代的行為
混合收集(Mixed GC):指目標(biāo)是收集整個(gè)新生代以及部分老年代的垃圾收集,目前只有G1收集器會(huì)有這種行為
整堆收集(Full GC):收集整個(gè)Java堆和方法區(qū)的垃圾收集
如何回收?垃圾收集算法
標(biāo)記-清除算法(老年代)
首先標(biāo)記出所有(不)需要回收的對(duì)象,在標(biāo)記完成后,統(tǒng)一回收掉所有被標(biāo)記的對(duì)象,也可以反過(guò)來(lái),標(biāo)記存活的對(duì)象,統(tǒng)一回收所有未被標(biāo)記的對(duì)象
缺點(diǎn):
1、執(zhí)行效率不穩(wěn)定,如果Java堆中包含大量對(duì)象,而且其中大部分是需要被回收的,這時(shí)必須進(jìn)行大量標(biāo)記和清除的動(dòng)作,導(dǎo)致標(biāo)記和清除兩個(gè)過(guò)程的執(zhí)行效率都隨對(duì)象數(shù)量增長(zhǎng)而降低
2、內(nèi)存空間碎片問(wèn)題,標(biāo)記、清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致當(dāng)以后在程序運(yùn)行過(guò)程中需要分配較大對(duì)象時(shí)無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作,分配時(shí)需要使用空閑分配鏈表
標(biāo)記-復(fù)制算法(新生代)
將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過(guò)的內(nèi)存空間一次清理掉
如果內(nèi)存中多數(shù)對(duì)象都是存活的,這種算法將會(huì)產(chǎn)生大量的內(nèi)存間復(fù)制的開(kāi)銷,但對(duì)于多數(shù)對(duì)象都是可回收的情況,算法需要復(fù)制的就是占少數(shù)的存活對(duì)象,而且每次都是針對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,分配內(nèi)存時(shí)也就不用考慮有空間碎片的復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配即可
這種復(fù)制回收算法的代價(jià)是將可用內(nèi)存縮小為了原來(lái)的一半,空間浪費(fèi)
改進(jìn):把新生代分為一塊較大的Eden空間和兩塊較小的 Survivor空間,每次分配內(nèi)存只使用Eden和其中一塊Survivor,發(fā)生垃圾收集時(shí),將Eden和Survivor中仍然存活的對(duì)象一次性復(fù)制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過(guò)的那塊Survivor空間
需要依賴其他內(nèi)存區(qū)域(老年代)進(jìn)行分配擔(dān)保
標(biāo)記-整理算法(老年代)
標(biāo)記完對(duì)象后,不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向內(nèi)存空間一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存
存活對(duì)象太多,移動(dòng)負(fù)擔(dān)重,對(duì)象移動(dòng)操作必須全程暫停用戶應(yīng)用程序才能進(jìn)行
從垃圾收集的停頓時(shí)間來(lái)看,不移動(dòng)對(duì)象停頓時(shí)間會(huì)更短,甚至可以不需要停頓,但是從整個(gè)程序的吞吐量來(lái)看,移動(dòng)對(duì)象會(huì)更劃算
CMS收集器:虛擬機(jī)平時(shí)多數(shù)時(shí)間都采用標(biāo)記-清除算法,暫時(shí)容忍內(nèi)存碎片的存在,直到內(nèi)存空間的碎片化程度已經(jīng)大到影響對(duì)象分配時(shí),再采用標(biāo)記-整理算法收集一次,以獲得規(guī)整的內(nèi)存空間

JVM-HotSpot垃圾收集算法細(xì)節(jié)

根節(jié)點(diǎn)枚舉:
1、固定可作為根節(jié)點(diǎn)的主要在全局性的引用(如常量或類變量)與執(zhí)行上下文(如棧幀中的本地變量表)中
2、所有收集器在根節(jié)點(diǎn)枚舉這一步驟時(shí)都是必須暫停用戶線程的,但是可達(dá)性分析算法耗時(shí)最長(zhǎng)的查找引用鏈的過(guò)程已經(jīng)可以做到與用戶線程一起并發(fā)
3、在HotSpot中,使用OopMap的數(shù)據(jù)結(jié)構(gòu)記錄哪些地方存放著對(duì)象引用,HotSpot并不是為每一條指令都生成對(duì)應(yīng)的OopMap(消耗內(nèi)存空間),而是在安全點(diǎn)(“長(zhǎng)時(shí)間執(zhí)行”的地方,如方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等都屬于指令序列復(fù)用,程序會(huì)檢查垃圾收集標(biāo)志位,如果需要垃圾收集,主動(dòng)停頓在這里,等待垃圾收集);但是并不是所有程序都能執(zhí)行到安全點(diǎn),如程序“不執(zhí)行”(就是沒(méi)有分配處理器時(shí)間,如用戶線程處于Sleep狀態(tài)或者Blocked狀態(tài))的時(shí)候線程無(wú)法響應(yīng)虛擬機(jī)的中斷請(qǐng)求,就不能再走到安全點(diǎn)去中斷掛起自己,虛擬機(jī)也顯然不可能持續(xù)等待線程被喚醒重新獲取時(shí)間片,再到安全點(diǎn),因此把這行場(chǎng)景設(shè)置為安全區(qū)域,當(dāng)程序執(zhí)行到這些區(qū)域時(shí),會(huì)標(biāo)識(shí)自己已經(jīng)進(jìn)入了安全區(qū)域,當(dāng)這段時(shí)間里虛擬機(jī)要發(fā)起垃圾收集時(shí)就不必去管這些已聲明自己在安全區(qū)域內(nèi)的線程,當(dāng)程序要離開(kāi)安全區(qū)域時(shí),它要檢查虛擬機(jī)是否已經(jīng)完成了根節(jié)點(diǎn)枚舉(或者垃圾收集過(guò)程中其他需要暫停用戶線程的階段),如果完成了,那程序就繼續(xù)執(zhí)行;否則它就必須一直等待,直到收到可以離開(kāi)安全區(qū)域的信號(hào)為止
4、跨代引用問(wèn)題:記憶集是一種用于記錄從非收集區(qū)域指向收集區(qū)域的指針集合的抽象數(shù)據(jù)結(jié)構(gòu)(解決跨代引用的問(wèn)題),最常用的實(shí)現(xiàn)形式是卡表(每個(gè)記錄精確到一塊內(nèi)存區(qū)域,該區(qū)域內(nèi)有對(duì)象含有跨代指針)(寫屏障,類似于切面)
3、并發(fā)的可達(dá)性分析:如果用戶線程與收集器是并發(fā)工作,這樣可能出現(xiàn)兩種后果:一種是把原本消亡的對(duì)象錯(cuò)誤標(biāo)記為存活, 只不過(guò)產(chǎn)生了一點(diǎn)逃過(guò)本次收集的浮動(dòng)垃圾而已,下次收集清理掉就好;另一種是把原本存活的對(duì)象錯(cuò)誤標(biāo)記為已消亡,非常致命
4、當(dāng)且僅當(dāng)以下兩個(gè)條件同時(shí)滿足時(shí),會(huì)產(chǎn)生“對(duì)象消失”的問(wèn)題: 賦值器插入了一條或多條從標(biāo)記對(duì)象到未標(biāo)記對(duì)象的新引用; 賦值器刪除了全部從標(biāo)記中對(duì)象到該未標(biāo)記對(duì)象的直接或間接引用(未標(biāo)記的對(duì)象與標(biāo)記對(duì)象新建立了引用關(guān)系)
要解決并發(fā)掃描時(shí)的對(duì)象消失問(wèn)題,只需破壞這兩個(gè)條件的任意一個(gè)即可
兩種解決方案:
1、增量更新要破壞的是第一個(gè)條件,當(dāng)標(biāo)記對(duì)象插入新的指向未標(biāo)記對(duì)象的引用關(guān)系時(shí),就將這個(gè)新插入的引用記錄下來(lái),等并發(fā)掃描結(jié)束之后,再將這些記錄過(guò)的引用關(guān)系中的標(biāo)記對(duì)象為根,重新掃描一次(CMS)
2、原始快照要破壞的是第二個(gè)條件,重新掃描一次指向刪除對(duì)象所有標(biāo)記對(duì)象(G1)

JVM-垃圾收集器

Serial收集器(新生代)單線程 標(biāo)記-復(fù)制算法
1、進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,直到收集結(jié)束
ParNew收集器(新生代)多線程 標(biāo)記-復(fù)制算法 與CMS配合使用
Parallel Scavenge收集器(新生代)多線程 標(biāo)記-復(fù)制算法
1、目標(biāo)是達(dá)到一個(gè)可控制的吞吐量
Serial Old收集器(老年代)單線程 標(biāo)記-整理算法
Parallel Old收集器 (老年代)多線程 標(biāo)記-整理算法
CMS收集器 (老年代)多線程 標(biāo)記-清除算法
目標(biāo)是獲取最短回收停頓時(shí)間
流程:
1、初始標(biāo)記:需要停頓時(shí)間,僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快
2、并發(fā)標(biāo)記:從GC Roots的直接關(guān)聯(lián)對(duì)象開(kāi)始遍歷整個(gè)對(duì)象圖的過(guò)程,這個(gè)過(guò)程耗時(shí)較長(zhǎng)但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)運(yùn)行
3、重新標(biāo)記:需要停頓時(shí)間,是為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄(增量更新),這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長(zhǎng)一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短
4、并發(fā)清除:清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對(duì)象,由于不需要移動(dòng)存活對(duì)象,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的
從總體上來(lái)說(shuō),CMS收集器的內(nèi)存回收過(guò)程是與用戶線程一起并發(fā)執(zhí)行的
缺點(diǎn):
1、對(duì)處理器資源非常敏感,在并發(fā)階段,它雖然不會(huì)導(dǎo)致用戶線程停頓,但卻會(huì)因?yàn)檎加昧艘徊糠志€程(或者說(shuō)處理器的計(jì)算能力)而導(dǎo)致應(yīng)用程序變慢,降低總吞吐量
2、無(wú)法處理“浮動(dòng)垃圾”,有可能出現(xiàn)并發(fā)失敗進(jìn)而導(dǎo)致另一次完全停頓線程的Full GC的產(chǎn)生
**在CMS的并發(fā)標(biāo)記和并發(fā)清理階段,用戶線程是還在繼續(xù)運(yùn)行的,程序在運(yùn)行自然就還會(huì)伴隨有新的垃圾對(duì)象不斷產(chǎn)生,但這一部分垃圾對(duì)象是出現(xiàn)在標(biāo)記過(guò)程結(jié)束以后,CMS無(wú)法在當(dāng)次收集中處理掉它們,只好留待下一次垃圾收集時(shí)再清理掉
**同樣也是由于在垃圾收集階段用戶線程還需要持續(xù)運(yùn)行,那就還需要預(yù)留足夠內(nèi)存空間提供給用戶線程使用,因此CMS收集器不能像其他收集器那樣等待到老年代幾乎完全被填滿了再進(jìn)行收集,必須預(yù)留一部分空間供并發(fā)收集時(shí)的程序運(yùn)作使用
**要是CMS運(yùn)行期間預(yù)留的內(nèi)存無(wú)法滿足程序分配新對(duì)象的需要,就會(huì)出現(xiàn)一次并發(fā)失敗,這時(shí)候虛擬機(jī)將不得不啟動(dòng)后備預(yù)案:凍結(jié)用戶線程的執(zhí)行,臨時(shí)啟用Serial Old收集器來(lái)重新進(jìn)行老年代的垃圾收集, 但這樣停頓時(shí)間就很長(zhǎng)了
3、收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生,空間碎片過(guò)多時(shí),將會(huì)給大對(duì)象分配帶來(lái)很大麻煩,往往會(huì)出現(xiàn)老年代還有很多剩余空間,但就是無(wú)法找到足夠大的連續(xù)空間來(lái)分配當(dāng)前對(duì)象,而不得不提前觸發(fā)一次Full GC的情況
G1收集器 (全堆)面向局部收集和基于Region(大小相等)的內(nèi)存布局,可以面向堆內(nèi)存任何部分來(lái)組成回收集進(jìn)行回收,Region可以作為新生代、老年代,不同階段可以有不同的角色,G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗鼘egion作為單次回收的最小單元,即每次收集到的內(nèi)存空間都是Region大小的整數(shù)倍
實(shí)現(xiàn)中的關(guān)鍵問(wèn)題:
1、將Java堆分成多個(gè)獨(dú)立Region后,Region里面存在的跨Region引用對(duì)象如何解決?使用記憶集避免全堆作為GC Roots掃描,每個(gè)Region都維護(hù)有自己的記憶集(浪費(fèi)內(nèi)存空間),這些記憶集會(huì)記錄下別的Region 指向自己的指針,并標(biāo)記這些指針?lè)謩e在哪些內(nèi)存區(qū)域
2、在并發(fā)標(biāo)記階段如何保證收集線程與用戶線程互不干擾地運(yùn)行?原始快照+指針(隔開(kāi)空閑空間和待收集空間)
流程:
1、初始標(biāo)記:僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,并且修改TAMS 指針的值,讓下一階段用戶線程并發(fā)運(yùn)行時(shí),能正確地在可用的Region中分配新對(duì)象,這個(gè)階段需要停頓線程,但耗時(shí)很短,而且是借用進(jìn)行Minor GC的時(shí)候同步完成的,所以G1收集器在這個(gè)階段實(shí)際并沒(méi)有額外的停頓
2、并發(fā)標(biāo)記:從GC Root開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆里的對(duì)象圖,找出要回收的對(duì)象,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行,當(dāng)對(duì)象圖掃描完成以后,還要重新處理SATB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對(duì)象(原始快照)
3、最終標(biāo)記:對(duì)用戶線程做另一個(gè)短暫的暫停,用于處理并發(fā)階段結(jié)束后仍遺留下來(lái)的最后那少量的SATB記錄
4、篩選回收:負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來(lái)制定回收計(jì)劃,可以自由選擇任意多個(gè)Region 構(gòu)成回收集,然后把決定回收的那一部分Region的存活對(duì)象復(fù)制到空的Region中,再清理掉整個(gè)舊Region的全部空間,這里的操作涉及存活對(duì)象的移動(dòng),是必須暫停用戶線程,由多條收集器線程并行完成的
G1從整體來(lái)看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器,但從局部(兩個(gè)Region 之間)上看又是基于“標(biāo)記-復(fù)制”算法實(shí)現(xiàn),無(wú)論如何,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片
缺點(diǎn):
1、在用戶程序運(yùn)行過(guò)程中,G1無(wú)論是為了垃圾收集產(chǎn)生的內(nèi)存占用還是程序運(yùn)行時(shí)的額外執(zhí)行負(fù)載都要比CMS要高
2、就內(nèi)存占用來(lái)說(shuō),雖然G1和CMS都使用卡表來(lái)處理跨代指針,但G1的卡表實(shí)現(xiàn)更為復(fù)雜,而且堆中每個(gè)Region,無(wú)論扮演的是新生代還是老年代角色,都必須有一份卡表,這導(dǎo)致G1的記憶集(和其他內(nèi)存消耗)占用更多的內(nèi)存空間;相比起來(lái)CMS的卡表就相當(dāng)簡(jiǎn)單, 只有唯一一份,而且只需要處理老年代到新生代的引用,反過(guò)來(lái)則不需要,由于新生代的對(duì)象具有朝生夕滅的不穩(wěn)定性,引用變化頻繁,能省下這個(gè)區(qū)域的維護(hù)開(kāi)銷是很劃算的
3、在執(zhí)行負(fù)載的角度上,同樣由于兩個(gè)收集器各自的細(xì)節(jié)實(shí)現(xiàn)特點(diǎn)導(dǎo)致了用戶程序運(yùn)行時(shí)的負(fù)載會(huì)有不同
**如它們都使用到寫屏障,CMS用寫后屏障來(lái)更新維護(hù)卡表;而G1除了使用寫后屏障來(lái)進(jìn)行同樣的卡表維護(hù)操作外,為了實(shí)現(xiàn)原始快照搜索 (SATB)算法,還需要使用寫前屏障來(lái)跟蹤并發(fā)時(shí)的指針變化情況
**相比起增量更新算法,原始快照搜索能夠減少并發(fā)標(biāo)記和重新標(biāo)記階段的消耗,避免CMS那樣在最終標(biāo)記階段停頓時(shí)間過(guò)長(zhǎng)的缺點(diǎn), 但是在用戶程序運(yùn)行過(guò)程中確實(shí)會(huì)產(chǎn)生由跟蹤引用變化帶來(lái)的額外負(fù)擔(dān),由于G1對(duì)寫屏障的復(fù)雜操作要比CMS消耗更多的運(yùn)算資源,所以CMS的寫屏障實(shí)現(xiàn)是直接的同步操作,而G1就不得不將其實(shí)現(xiàn)為類似于消息隊(duì)列的結(jié)構(gòu),把寫前屏障和寫后屏障中要做的事情都放到隊(duì)列里,然后再異步處理
4、CMS和G1分別使用增量更新和原始快照技術(shù),實(shí)現(xiàn)了標(biāo)記階段的并發(fā),不會(huì)因管理的堆內(nèi)存變大,要標(biāo)記的對(duì)象變多而導(dǎo)致停頓時(shí)間隨之增長(zhǎng),但是對(duì)于標(biāo)記階段之后的處理,仍未得到妥善解決,CMS使用標(biāo)記-清除算法,雖然避免了整理階段收集器帶來(lái)的停頓,但是清除算法不論如何優(yōu)化改進(jìn),在設(shè)計(jì)原理上避免不了空間碎片的產(chǎn)生,隨著空間碎片不斷淤積最終依然逃不過(guò)停頓線程的命運(yùn);G1雖然可以按更小的粒度進(jìn)行回收,從而抑制整理階段出現(xiàn)時(shí)間過(guò)長(zhǎng)的停頓,但畢竟也還是要暫停的
ZGC收集器 (全堆)
幾乎整個(gè)工作過(guò)程全部都是并發(fā)的,只有初始標(biāo)記、最終標(biāo)記這些階段有短暫的停頓,這部分停頓的時(shí)間基本上是固定的,與堆的容量、堆中對(duì)象的數(shù)量沒(méi)有正比例關(guān)系
ZGC收集器是一款基于Region內(nèi)存布局的,(暫時(shí)) 不設(shè)分代的,使用了讀屏障、染色指針和內(nèi)存多重映射等技術(shù)來(lái)實(shí)現(xiàn)可并發(fā)的標(biāo)記-整理算法的,以低延遲為首要目標(biāo)的一款垃圾收集器
內(nèi)存布局:
1、與G1一樣,ZGC也采用基于Region的堆內(nèi)存布局,但不同的是,ZGC的Region具有動(dòng)態(tài)性——?jiǎng)討B(tài)創(chuàng)建和銷毀,以及動(dòng)態(tài)的區(qū)域容量大?。ㄐ⌒?、中型固定)
2、大型Region容量不固定,可以動(dòng)態(tài)變化,每個(gè)大型Region中只會(huì)存放一個(gè)大對(duì)象,實(shí)際容量完全有可能小于中型Region,大型Region在ZGC的實(shí)現(xiàn)中是不會(huì)被重分配的,因?yàn)閺?fù)制一個(gè)大對(duì)象的代價(jià)非常高昂
并發(fā)整理算法的實(shí)現(xiàn)(染色指針技術(shù)):
1、從前,如果我們要在對(duì)象上存儲(chǔ)一些額外的、只供收集器或者虛擬機(jī)本身使用的數(shù)據(jù),通常會(huì)在對(duì)象頭中增加額外的存儲(chǔ)字段,如對(duì)象的哈希碼、分代年齡、鎖記錄等就是這樣存儲(chǔ)的,這種記錄方式在有對(duì)象訪問(wèn)的場(chǎng)景下是很自然流暢的,不會(huì)有什么額外負(fù)擔(dān)
2、但如果對(duì)象存在被移動(dòng)過(guò)的可能性,即不能保證對(duì)象訪問(wèn)能夠成功呢? 又或者有一些根本就不會(huì)去訪問(wèn)對(duì)象,但又希望得知該對(duì)象的某些信息的應(yīng)用場(chǎng)景呢?能不能從指針或者與對(duì)象內(nèi)存無(wú)關(guān)的地方得到這些信息,如是否能夠看出來(lái)對(duì)象被移動(dòng)過(guò)?追蹤式收集算法的標(biāo)記階段就可能存在只跟指針打交道而不必涉及指針?biāo)玫膶?duì)象本身的場(chǎng)景,如對(duì)象標(biāo)記的過(guò)程中需要給對(duì)象打上三色標(biāo)記,這些標(biāo)記本質(zhì)上就只和對(duì)象的引用有關(guān),而與對(duì)象本身無(wú)關(guān)——某個(gè)對(duì)象只有它的引用關(guān)系能決定它存活與否,對(duì)象上其他所有的屬性都不能夠影響它的存活判定結(jié)果
3、HotSpot虛擬機(jī)的幾種收集器有不同的標(biāo)記實(shí)現(xiàn)方案,有的把標(biāo)記直接記錄在對(duì)象頭上(如Serial收集器),有的把標(biāo)記記錄在與對(duì)象相互獨(dú)立的數(shù)據(jù)結(jié)構(gòu)上(如G1用稱為BitMap的結(jié)構(gòu)來(lái)記錄標(biāo)記信息),而ZGC的染色指針直接把標(biāo)記信息記在引用對(duì)象的指針上
4、通過(guò)指針上的標(biāo)志位,虛擬機(jī)可以直接從指針中看到其引用對(duì)象的三色標(biāo)記狀態(tài)、是否進(jìn)入了重分配集(即被移動(dòng)過(guò))、是否只能通過(guò)finalize()方法才能被訪問(wèn)到
優(yōu)點(diǎn):
**染色指針可以使得一旦某個(gè)Region的存活對(duì)象被移走之后,這個(gè)Region立即就能夠被釋放和重用掉,而不必等待整個(gè)堆中所有指向該Region的引用都被修正后才能清理;使得理論上只要還有一個(gè)空閑Region,ZGC就能完成收集
**染色指針可以大幅減少在垃圾收集過(guò)程中內(nèi)存屏障的使用數(shù)量,設(shè)置內(nèi)存屏障,尤其是寫屏障的目的通常是為了記錄對(duì)象引用的變動(dòng)情況,如果將這些信息直接維護(hù)在指針中,顯然就可以省去一些專門的記錄操作,實(shí)際上,到目前為止ZGC都并未使用任何寫屏障,只使用了讀屏障(一部分是染色指針的功勞,一部分是ZGC現(xiàn)在還不支持分代收集,天然就沒(méi)有跨代引用的問(wèn)題),所以ZGC對(duì)吞吐量的影響也相對(duì)較低
**染色指針可以作為一種可擴(kuò)展的存儲(chǔ)結(jié)構(gòu)用來(lái)記錄更多與對(duì)象標(biāo)記、重定位過(guò)程相關(guān)的數(shù)據(jù),以便日后進(jìn)一步提高性能
Java虛擬機(jī)作為一個(gè)普普通通的進(jìn)程, 這樣隨意重新定義內(nèi)存中某些指針的其中幾位,操作系統(tǒng)是否支持?處理器是否支持?需要使用虛擬內(nèi)存映射技術(shù)解決
ZGC使用了多重映射將多個(gè)不同的虛擬內(nèi)存地址映射到同一個(gè)物理內(nèi)存地址上,這是一種多對(duì)一映射,意味著ZGC在虛擬內(nèi)存中看到的地址空間要比實(shí)際的堆內(nèi)存容量來(lái)得更大,把染色指針中的標(biāo)志位看作是地址的分段符,那只要將這些不同的地址段都映射到同一個(gè)物理內(nèi)存空間,經(jīng)過(guò)多重映射轉(zhuǎn)換后,就可以使用染色指針正常進(jìn)行尋址了
流程:
1、并發(fā)標(biāo)記:并發(fā)標(biāo)記是遍歷對(duì)象圖做可達(dá)性分析的階段,前后也要經(jīng)過(guò)初始標(biāo)記、最終標(biāo)記的短暫停頓,ZGC 的標(biāo)記是在指針上而不是在對(duì)象上進(jìn)行的,標(biāo)記階段會(huì)更新染色指針中的標(biāo)志位
2、并發(fā)預(yù)備重分配:這個(gè)階段需要根據(jù)特定的查詢條件統(tǒng)計(jì)得出本次收集過(guò)程要清理哪些Region,將這些Region組成重分配集,ZGC劃分Region的目的并非為了像G1那樣做收益優(yōu)先的增量回收,相反,ZGC每次回收都會(huì)掃描所有的Region,用范圍更大的掃描成本換取省去G1中記憶集的維護(hù)成本;因此,ZGC的重分配集只是決定了里面的存活對(duì)象會(huì)被重新復(fù)制到其他的Region中,里面的Region會(huì)被釋放,而并不能說(shuō)回收行為就只是針對(duì)這個(gè)集合里面的Region進(jìn)行,因?yàn)闃?biāo)記過(guò)程是針對(duì)全堆的
3、并發(fā)重分配:重分配是ZGC執(zhí)行過(guò)程中的核心階段
--這個(gè)過(guò)程要把重分配集中的存活對(duì)象復(fù)制到新的Region上,并為重分配集中的每個(gè)Region維護(hù)一個(gè)轉(zhuǎn)發(fā)表,記錄從舊對(duì)象到新對(duì)象的轉(zhuǎn)向關(guān)系
--得益于染色指針的支持,ZGC收集器能僅從引用上就明確得知一個(gè)對(duì)象是否處于重分配集之中,如果用戶線程此時(shí)并發(fā)訪問(wèn)了位于重分配集中的對(duì)象,這次訪問(wèn)將會(huì)被預(yù)置的內(nèi)存屏障所截獲,然后立即根據(jù)Region上的轉(zhuǎn)發(fā)表記錄將訪問(wèn)轉(zhuǎn)發(fā)到新復(fù)制的對(duì)象上,并同時(shí)修正更新該引用的值,使其直接指向新對(duì)象,ZGC將這種行為稱為指針的“自愈”能力
--這樣做的好處是只有第一次訪問(wèn)舊對(duì)象會(huì)陷入轉(zhuǎn)發(fā),也就是只慢一次
--由于染色指針的存在,一旦重分配集中某個(gè)Region的存活對(duì)象都復(fù)制完畢后,這個(gè)Region就可以立即釋放用于新對(duì)象的分配(但是轉(zhuǎn)發(fā)表還得留著不能釋放掉),哪怕堆中還有很多指向這個(gè)對(duì)象的未更新指針也沒(méi)有關(guān)系,這些舊指針一旦被使用,它們都是可以自愈的
4、并發(fā)重映射:重映射所做的就是修正整個(gè)堆中指向重分配集中舊對(duì)象的所有引用
--并不是一個(gè)必須要“迫切”去完成的任務(wù),因?yàn)榧词故桥f引用,它也是可以自愈的,最多只是第一次使用時(shí)多一次轉(zhuǎn)發(fā)和修正操作,重映射清理這些舊引用的主要目的是為了不變慢(還有清理結(jié)束后可以釋放轉(zhuǎn)發(fā)表這樣的附帶收益),所以說(shuō)這并不是很“迫切”
--ZGC把并發(fā)重映射階段要做的工作,合并到了下一次垃圾收集循環(huán)中的并發(fā)標(biāo)記階段里去完成,反正它們都是要遍歷所有對(duì)象的,這樣合并就節(jié)省了一次遍歷對(duì)象圖的開(kāi)銷,一旦所有指針都被修正之后,原來(lái)記錄新舊對(duì)象關(guān)系的轉(zhuǎn)發(fā)表就可以釋放掉了
優(yōu)缺點(diǎn):
1、ZGC完全沒(méi)有使用記憶集,它甚至連分代都沒(méi)有,連像CMS中那樣只記錄新生代和老年代間引用的卡表也不需要,因而完全沒(méi)有用到寫屏障,所以給用戶線程帶來(lái)的運(yùn)行負(fù)擔(dān)也要小得多
2、ZGC的這種選擇也限制了它能承受的對(duì)象分配速率不會(huì)太高,ZGC準(zhǔn)備要對(duì)一個(gè)很大的堆做一次完整的并發(fā)收集,假設(shè)其全過(guò)程要持續(xù)十分鐘以上,在這段時(shí)間里面
由于應(yīng)用的對(duì)象分配速率很高,將創(chuàng)造大量的新對(duì)象,這些新對(duì)象很難進(jìn)入當(dāng)次收集的標(biāo)記范 圍,通常就只能全部當(dāng)作存活對(duì)象來(lái)看待——盡管其中絕大部分對(duì)象都是朝生夕滅的,這就產(chǎn)生了大量的浮動(dòng)垃圾,如果這種高速分配持續(xù)維持的話,每一次完整的并發(fā)收集周期都會(huì)很長(zhǎng),回收到的內(nèi)存空間持續(xù)小于期間并發(fā)產(chǎn)生的浮動(dòng)垃圾所占的空間,堆中剩余可騰挪的空間就越來(lái)越小了
3、目前唯一的辦法就是盡可能地增加堆容量大小,獲得更多喘息的時(shí)間,但是若要從根本上提升ZGC能夠應(yīng)對(duì) 的對(duì)象分配速率,還是需要引入分代收集,讓新生對(duì)象都在一個(gè)專門的區(qū)域中創(chuàng)建,然后專門針對(duì)這個(gè)區(qū)域進(jìn)行更頻繁、更快的收集
內(nèi)存分配與回收策略
對(duì)象的內(nèi)存分配,應(yīng)該都是在堆上分配(而實(shí)際上也有可能經(jīng)過(guò)即時(shí)編譯后被拆散 為標(biāo)量類型并間接地在棧上分配)
在經(jīng)典分代的設(shè)計(jì)下,新生對(duì)象通常會(huì)分配在新生代中,少數(shù)情況下(如對(duì)象大小超過(guò)一定閾值)也可能會(huì)直接分配在老年代
1、大對(duì)象直接進(jìn)入老年代:在Java虛擬機(jī)中要避免大對(duì)象的原因是,在分配空間時(shí),它容易導(dǎo)致內(nèi)存明明還有不少空間時(shí)就提前觸發(fā)垃圾收集,以獲取足夠的連續(xù)空間才能安置好它們,而當(dāng)復(fù)制對(duì)象時(shí),大對(duì)象就意味著高額的內(nèi)存復(fù)制開(kāi)銷
2、長(zhǎng)期存活的對(duì)象將進(jìn)入老年代:對(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ì)被晉升到老年代中
3、空間分配擔(dān)保:
--在發(fā)生Minor GC之前,虛擬機(jī)必須先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那這一次Minor GC可以確保是安全的
--如果不成立,會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險(xiǎn)的;如果小于,就要進(jìn)行一次Full GC

JVM-逃逸分析

逃逸分析的基本原理
1、分析對(duì)象動(dòng)態(tài)作用域,當(dāng)一個(gè)對(duì)象在方法里面被定義后,它可能被外部方法所引用,如作為調(diào)用參數(shù)傳遞到其他方法中,這種稱為方法逃逸;甚至還有可能被外部線程訪問(wèn)到,如賦值給可以在其他線程中訪問(wèn)的實(shí)例變量,這種稱為線程逃逸;從不逃逸、方法逃逸到線程逃逸,稱為對(duì)象由低到高的不同逃逸程度
2、如果能證明一個(gè)對(duì)象不會(huì)逃逸到方法或線程之外(即別的方法或線程無(wú)法通過(guò)任何途徑訪問(wèn)到這個(gè)對(duì)象),或者逃逸程度比較低(只逃逸出方法而不會(huì)逃逸出線程),則可能為這個(gè)對(duì)象實(shí)例采取不同程度的優(yōu)化
棧上分配
1、在Java虛擬機(jī)中,Java堆中的對(duì)象對(duì)于各個(gè)線程都是共享和可見(jiàn)的,只要持有這個(gè)對(duì)象的引用,就可以訪問(wèn)到堆中存儲(chǔ)的對(duì)象數(shù)據(jù)
2、虛擬機(jī)的垃圾收集子系統(tǒng)會(huì)回收堆中不再使用的對(duì)象,但回收動(dòng)作無(wú)論是標(biāo)記篩選出可回收對(duì)象,還是回收和整理內(nèi)存,都需要耗費(fèi)大量資源
3、如果確定一個(gè)對(duì)象不會(huì)逃逸出線程之外,就可以將這個(gè)對(duì)象在棧上分配內(nèi)存,對(duì)象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀
4、實(shí)際上完全不會(huì)逃逸的局部對(duì)象和不會(huì)逃逸出線程的對(duì)象所占的比例是很大的,如果能使用棧上分配,那大量的對(duì)象就會(huì)隨著方法的結(jié)束而自動(dòng)銷毀了,垃圾收集子系統(tǒng)的壓力將會(huì)下降很多
5、棧上分配可以支持方法逃逸,但不能支持線程逃逸
標(biāo)量替換
1、若一個(gè)數(shù)據(jù)已經(jīng)無(wú)法再分解成更小的數(shù)據(jù)來(lái)表示了,Java虛擬機(jī)中的原始數(shù)據(jù)類型(int、long等數(shù)值類型及reference類型等)都不能再進(jìn)一步分解了,那么這些數(shù)據(jù) 就可以被稱為標(biāo)量,相對(duì)的,如果一個(gè)數(shù)據(jù)可以繼續(xù)分解,那它就被稱為聚合量,Java 中的對(duì)象就是典型的聚合量
2、如果把一個(gè)Java對(duì)象拆散,根據(jù)程序訪問(wèn)的情況,將其用到的成員變量恢復(fù)為原始類型來(lái)訪問(wèn),這個(gè)過(guò)程就稱為標(biāo)量替換
3、如果逃逸分析能夠證明一個(gè)對(duì)象不會(huì)被方法外部訪問(wèn),并且這個(gè)對(duì)象可以被拆散,那么程序真正執(zhí)行的時(shí)候?qū)⒖赡懿蝗?chuàng)建這個(gè)對(duì)象,而改為直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用的成員變量來(lái)代替
4、將對(duì)象拆分后,除了可以讓對(duì)象的成員變量在棧上 (棧上存儲(chǔ)的數(shù)據(jù),很大機(jī)會(huì)被虛擬機(jī)分配至物理機(jī)器的高速寄存器中存儲(chǔ))分配和讀寫之外,還可以為后續(xù)進(jìn)一步的優(yōu)化手段創(chuàng)建條件
5、標(biāo)量替換可以視作棧上分配的一種特例,實(shí)現(xiàn)更簡(jiǎn)單(不用考慮整個(gè)對(duì)象完整結(jié)構(gòu)的分配),但對(duì)逃逸程度的要求更高,它不允許對(duì)象逃逸出方法范圍內(nèi)
同步消除
線程同步本身是一個(gè)相對(duì)耗時(shí)的過(guò)程,如果逃逸分析能夠確定一個(gè)變量不會(huì)逃逸出線程,無(wú)法被其他線程訪問(wèn),那么這個(gè)變量的讀寫肯定就不會(huì)有競(jìng)爭(zhēng), 對(duì)這個(gè)變量實(shí)施的同步措施也就可以安全地消除掉
后端編譯優(yōu)化步驟
1、方法內(nèi)聯(lián)優(yōu)化
2、逃逸分析
3、無(wú)效代碼刪除

線程-java內(nèi)存模型(共享內(nèi)存模型)

定義
1、在并發(fā)編程中,需要處理兩個(gè)關(guān)鍵問(wèn)題:線程之間如何通信及同步;而Java線程之間的通信由Java內(nèi)存模型控制,Java內(nèi)存模型決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見(jiàn)(但是會(huì)存在內(nèi)存可見(jiàn)性問(wèn)題,需要通過(guò)顯式的同步機(jī)制去處理線程間的執(zhí)行順序(相對(duì))問(wèn)題)
2、Java內(nèi)存模型將所有的共享變量都存儲(chǔ)在主內(nèi)存中(虛擬機(jī)內(nèi)存的一部分),每個(gè)線程還有自己的工作內(nèi)存
3、線程的工作內(nèi)存中保存了被該線程使用的共享變量的主內(nèi)存副本( 不是整個(gè)對(duì)象,而是對(duì)象中具體的變量),線程對(duì)共享變量的所有讀寫操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的數(shù)據(jù)
4、不同的線程之間無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成(線程A把本地內(nèi)存中更新過(guò)的共享變量刷新到主內(nèi)存中去,線程B到主內(nèi)存中去讀取線程A之前已更新過(guò)的共享變量)
順序一致性內(nèi)存模型(理論模型)
特性:
1、一個(gè)線程中的所有操作必須按照程序的順序來(lái)執(zhí)行
2、無(wú)論程序是否同步,所有線程都只能看到一個(gè)單一的操作執(zhí)行順序;在順序一致性內(nèi)存模型中,每個(gè)操作都必須原子執(zhí)行且立刻對(duì)所有線程可見(jiàn)
JMM特性:
1、未同步程序在JMM中不但整體的執(zhí)行順序是無(wú)序的,而且所有線程看到的操作執(zhí)行順序也可能不一致
2、當(dāng)前線程把數(shù)據(jù)緩存在本地內(nèi)存中,在沒(méi)有刷新到主內(nèi)存之前,這個(gè)寫操作僅對(duì)當(dāng)前線程可見(jiàn);只有當(dāng)前線程把本地內(nèi)存中寫過(guò)的數(shù)據(jù)刷新到主內(nèi)存之后,這個(gè)寫操作才能對(duì)其他線程可見(jiàn);因此,當(dāng)前線程和其他線程看到的操作執(zhí)行順序?qū)⒉灰恢?br> 3、臨界區(qū)內(nèi)的代碼可以重排序(但JMM不允許臨界區(qū)內(nèi)的代碼“逸出”到臨界區(qū)之外,那樣會(huì)破壞監(jiān)視器的語(yǔ)義),JMM會(huì)在退出臨界區(qū)和進(jìn)入臨界區(qū)這兩個(gè)關(guān)鍵時(shí)間點(diǎn)做一些特別處理,雖然線程在臨界區(qū)內(nèi)做了重排序,但由于監(jiān)視器互斥執(zhí)行的特性,其他線程看不到當(dāng)前線程在臨界區(qū)內(nèi)的重排序
4、JMM不保證單線程內(nèi)的操作會(huì)按程序的順序執(zhí)行;JMM不保證所有線程能看到一致的操作執(zhí)行順序

原子性、可見(jiàn)性、有序性
Java內(nèi)存模型需要處理3個(gè)問(wèn)題:原子性、可見(jiàn)性和有序性
原子性:
1、由JMM來(lái)直接保證的原子性變量操作包括read、load、assign、use、store和write這六個(gè), 基本數(shù)據(jù)類型的訪問(wèn)、讀寫都是具備原子性的(例外就是long和double的非原子性協(xié)定)
2、如果應(yīng)用場(chǎng)景需要一個(gè)更大范圍的原子性保證(經(jīng)常會(huì)遇到),JMM還提供了lock和unlock操作來(lái)滿足這種需求,盡管虛擬機(jī)未把lock和unlock操作直接開(kāi)放給用戶使用,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來(lái)隱式地使用這兩個(gè)操作;這兩個(gè)字節(jié)碼指令反映到Java 代碼中就是同步塊——synchronized關(guān)鍵字,因此在synchronized塊之間的操作也具備原子性
可見(jiàn)性:
1、可見(jiàn)性就是指當(dāng)一個(gè)線程修改了共享變量的值時(shí),其他線程能夠立即得知這個(gè)修改---JMM是通過(guò)在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存通信的方式來(lái)實(shí)現(xiàn)可見(jiàn)性的,無(wú)論是普通變量還是volatile變量都是如此
2、普通變量與volatile變量的區(qū)別是,volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新;因此我們可以說(shuō)volatile保證了多線程操作 時(shí)變量的可見(jiàn)性,而普通變量則不能保證這一點(diǎn)
3、同步塊的可見(jiàn)性是由“對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store、write操作)”這條規(guī)則獲得的
4、final關(guān)鍵字的可見(jiàn)性是指:被final修飾的字段在構(gòu)造器中一旦被初始化完成,并且構(gòu)造器沒(méi)有把“this”的引用傳遞出去(this引用逃逸是一件很危險(xiǎn)的事情,其他線程有可能通過(guò)這個(gè)引用訪問(wèn)到“初始化了一半”的對(duì)象),那么在其他線程中就能看見(jiàn)final字段的值
有序性:
1、線程內(nèi)所有的操作都是有序的(串行語(yǔ)義);線程間所有的操作是無(wú)序的(指令重排序、工作內(nèi)存與主內(nèi)存同步延遲)
2、Java語(yǔ)言提供了volatile和synchronized兩個(gè)關(guān)鍵字來(lái)保證線程之間操作的有序性,---volatile關(guān)鍵字本身就包含了禁止指令重排序的語(yǔ)義
3、synchronized則是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”這條規(guī)則獲得的,這個(gè)規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行地進(jìn)入
先行發(fā)生原則-happens-before
定義:在JMM中,如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見(jiàn),那么這兩個(gè)操作之間必須要存在先行發(fā)生關(guān)系,先行發(fā)生原則用于描述內(nèi)存可見(jiàn)性;兩個(gè)操作既可以是在一個(gè)線程之內(nèi),也可以是在不同線程之間
兩個(gè)操作之間具有先行發(fā)生關(guān)系,并不意味著前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行,先行發(fā)生僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對(duì)后一個(gè)操作可見(jiàn),且前一個(gè)操作按順序排在第二個(gè)操作之前
JMM先行發(fā)生(不是時(shí)間上的先后)規(guī)則:
1、程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,先行發(fā)生于該線程中的任意后續(xù)操作
2、監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖,先行發(fā)生(時(shí)間上的先后)于隨后對(duì)這個(gè)鎖的加鎖
3、volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫,先行發(fā)生(時(shí)間上的先后)于任意后續(xù)對(duì)這個(gè)volatile變量的 讀
4、Thread對(duì)象的start方法先行發(fā)生于此線程的每一個(gè)動(dòng)作
5、線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè)
6、對(duì)線程interrupt方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生
7、一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的 finalize方法的開(kāi)始
8、傳遞性:如果A先行發(fā)生于B,且B先行發(fā)生于C,那么A先行發(fā)生于C

volatile(可見(jiàn)性、有序性)

volatile讀:如果主內(nèi)存變量值有更新,將本地內(nèi)存中變量值失效,直接從主內(nèi)存中讀取最新值
volatile寫:更新本地內(nèi)存的同時(shí),會(huì)立即刷新到主內(nèi)存(主內(nèi)存更新時(shí)會(huì)通知其他線程)

synchronized(可見(jiàn)性、有序性、原子性,互斥可重入,鎖是存放在對(duì)象頭里面的)

同步塊:monitorenter和monitorexit指令實(shí)現(xiàn)
同步方法:依靠方法修飾符上的ACC_SYNCHRONIZEN同步位來(lái)實(shí)現(xiàn)
如果線程獲取鎖失敗,線程進(jìn)入同步隊(duì)列,線程狀態(tài)變?yōu)锽LOCKED
當(dāng)獲取鎖的線程釋放了鎖,會(huì)同時(shí)喚醒阻塞在同步隊(duì)列中的線程,使其重新嘗試對(duì)鎖的獲取
等待通知機(jī)制:
wait方法:前提是已經(jīng)獲取了鎖,調(diào)用該方法的線程從運(yùn)行狀態(tài)變成等待狀態(tài)(線程進(jìn)入等待隊(duì)列)并釋放鎖,只有等待其他線程的通知或中斷,超時(shí)才會(huì)返回
notify方法:前提是已經(jīng)獲取了鎖,將等待的線程從等待隊(duì)列移到同步隊(duì)列(等待狀態(tài)變成阻塞狀態(tài)),但當(dāng)前線程可能還未執(zhí)行完并釋放鎖,等待的線程不會(huì)從wait方法返回(獲取鎖才會(huì)返回)


image.png

鎖升級(jí)(不能降級(jí)):
1、無(wú)鎖
2、偏向鎖:Mark Word中存偏向線程id、對(duì)象分代年齡、是否是偏向鎖標(biāo)志、鎖標(biāo)志位;單線程時(shí),避免了CAS加鎖/解鎖,只需判斷偏向線程id是否是自己,如果不是,且現(xiàn)在是偏向鎖,則CAS將偏向線程id替換為自己,否則,CAS競(jìng)爭(zhēng)鎖,到全局安全點(diǎn),暫停偏向線程,釋放鎖,鎖變成無(wú)鎖狀態(tài)(偏向線程已經(jīng)執(zhí)行完邏輯)或輕量級(jí)鎖
3、輕量級(jí)鎖:Mark Word中存放指向持有鎖線程的棧中鎖記錄的指針、鎖標(biāo)志位;CAS替換鎖記錄指針,失敗,則自旋(避免上下文切換)+CAS獲取鎖,超過(guò)一定時(shí)間(次數(shù))膨脹為重量級(jí)鎖;追求響應(yīng)時(shí)間
4、重量級(jí)鎖:Mark Word中存指向獲取鎖的線程的棧中鎖記錄的指針、鎖標(biāo)志位;線程阻塞,不使用自旋,不消耗CPU;追求吞吐量

final(可見(jiàn)性、有序性、原子性)

final寫:在構(gòu)造方法內(nèi)的final寫與被構(gòu)造對(duì)象的引用賦值,禁止重排序
final讀:初次讀對(duì)象引用與隨后讀對(duì)象的final常量,禁止重排序

JUC-AQS

1、AQS支持獨(dú)占鎖(如ReentrantLock、ReadWriteLock的寫鎖)和共享鎖(如CountDownLatch、ReadWriteLock的讀鎖)
2、state:表示資源數(shù),volatile修飾,需要保證可見(jiàn)性、有序性、利用CAS保證原子性操作
3、隊(duì)列節(jié)點(diǎn)等待狀態(tài):初始狀態(tài)、取消狀態(tài)(線程超時(shí)或被中斷時(shí),會(huì)進(jìn)入取消狀態(tài),取消狀態(tài)不會(huì)再往下執(zhí)行)、喚醒狀態(tài)(表示當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)正在等待獲取資源,當(dāng)前節(jié)點(diǎn)在release或cancel時(shí)需要執(zhí)行unpark來(lái)喚醒后繼節(jié)點(diǎn))、條件狀態(tài)(當(dāng)前節(jié)點(diǎn)為條件隊(duì)列節(jié)點(diǎn),這個(gè)狀態(tài)在同步隊(duì)列里不會(huì)被用到)、傳播狀態(tài)(針對(duì)共享鎖,設(shè)置在head節(jié)點(diǎn)releaseShared(釋放共享鎖)操作需要被傳遞到下一個(gè)節(jié)點(diǎn),用來(lái)保證后繼節(jié)點(diǎn)可以獲取共享資源)
4、nextWaiter屬性:連接下一個(gè)等待condition的節(jié)點(diǎn),或者在共享模式下作為一個(gè)特殊節(jié)點(diǎn)保存,用來(lái)判斷是否為共享模式
5、同步隊(duì)列:雙向鏈表,隊(duì)尾插入時(shí)會(huì)有線程競(jìng)爭(zhēng)(自旋+CAS插入,需要前驅(qū)有效節(jié)點(diǎn)喚醒,使用LockSupport.park/unpark阻塞釋放線程);隊(duì)首表示獲取資源的線程節(jié)點(diǎn)
**acquire方法:調(diào)用子類的tryAcquire方法嘗試CAS獲取資源,成功直接返回;失敗則阻塞線程,創(chuàng)建節(jié)點(diǎn),自旋+CAS將節(jié)點(diǎn)放入同步隊(duì)列尾部,當(dāng)前驅(qū)有效節(jié)點(diǎn)是頭節(jié)點(diǎn)時(shí),自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點(diǎn)不為頭節(jié)點(diǎn)時(shí),阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點(diǎn)喚醒;即使當(dāng)前線程中間被中斷過(guò),也可以自旋+CAS嘗試獲取資源);如果自旋+CAS嘗試獲取資源出現(xiàn)異常,要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消
**release方法:調(diào)用子類的tryRelease方法嘗試自旋+CAS釋放資源,喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)(中間會(huì)移除取消狀態(tài)的節(jié)點(diǎn)),后繼節(jié)點(diǎn)自旋+CAS嘗試獲取資源,獲取成功,則從acquire方法返回
**acquireShared方法:調(diào)用子類的tryAcquireShared方法嘗試CAS獲取資源,成功直接返回;失敗則阻塞線程,創(chuàng)建節(jié)點(diǎn),自旋+CAS將節(jié)點(diǎn)放入同步隊(duì)列尾部,當(dāng)前驅(qū)有效節(jié)點(diǎn)是頭節(jié)點(diǎn)時(shí),自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點(diǎn)不為頭節(jié)點(diǎn)時(shí),阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點(diǎn)喚醒;即使當(dāng)前線程中間被中斷過(guò),也可以自旋+CAS嘗試獲取資源),獲取資源成功后,將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),如果還有剩余資源,讓后續(xù)節(jié)點(diǎn)也嘗試獲取資源;如果自旋+CAS嘗試獲取資源出現(xiàn)異常(超時(shí)),要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消
**releaseShared方法:調(diào)用子類的tryReleaseShared方法嘗試自旋+CAS釋放資源,喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)(中間會(huì)移除取消狀態(tài)的節(jié)點(diǎn)),后繼節(jié)點(diǎn)自旋+CAS嘗試獲取資源,獲取成功,則從acquireShared方法返回
6、子類需要實(shí)現(xiàn)的方法:tryAcquire/tryRelease(獨(dú)占)、tryAcquireShared/tryReleaseShared(共享)、isHeldExclusively(有用到Condition才需要實(shí)現(xiàn))
7、條件隊(duì)列:雙向鏈表,只有在使用了Condition(AQS的內(nèi)部類)才會(huì)存在條件隊(duì)列,在使用Condition的方法之前需要先獲取鎖
**await方法:調(diào)用之前當(dāng)前線程需要先獲取資源;創(chuàng)建節(jié)點(diǎn),自旋+CAS將節(jié)點(diǎn)放入條件隊(duì)列尾部;調(diào)用release方法釋放當(dāng)前線程已經(jīng)持有的資源,并移除同步隊(duì)列節(jié)點(diǎn),喚醒同步隊(duì)列中的后繼節(jié)點(diǎn),如果釋放資源出現(xiàn)異常(超時(shí)),要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消;當(dāng)當(dāng)前線程沒(méi)有加入到同步隊(duì)列時(shí),進(jìn)行自旋,并阻塞當(dāng)前線程,直到當(dāng)前線程被喚醒(從條件隊(duì)列移到同步隊(duì)列)或期間被中斷,并記錄中斷狀態(tài);在同步隊(duì)列中自旋+CAS嘗試獲取資源;如果當(dāng)前節(jié)點(diǎn)的nextWaiter不為空,說(shuō)明節(jié)點(diǎn)在獲取鎖時(shí)由于異常或者被中斷而被取消,此時(shí)需要移除等待隊(duì)列中取消狀態(tài)的節(jié)點(diǎn);如果期間被中斷過(guò),拋出中斷異常
**signal方法:自旋+CAS將條件隊(duì)列中節(jié)點(diǎn)移除,并創(chuàng)建新節(jié)點(diǎn),添加到同步隊(duì)列尾部,并喚醒線程,在同步中自旋+CAS嘗試獲取資源,獲取成功從await方法返回

JUC-ReentrantLock

lock是顯式的獲取鎖,擁有鎖獲取與釋放的可操作性、非阻塞獲取鎖、可中斷的獲取鎖(獲取到鎖的線程能響應(yīng)中斷:當(dāng)獲取到鎖的線程被中斷時(shí),鎖也會(huì)被釋放)以及超時(shí)獲取鎖(如果超時(shí)未獲取鎖,會(huì)返回)等多種synchronized關(guān)鍵字所不具備的同步特性;不能將獲取lock鎖的過(guò)程寫在try塊中,因?yàn)槿绻讷@取鎖時(shí)發(fā)生了異常(已經(jīng)獲取鎖),異常拋出時(shí),鎖也會(huì)被釋放;在finally塊中釋放鎖
ReentrantLock是一個(gè)可重入的互斥鎖,也被稱為“獨(dú)占鎖”,“獨(dú)占鎖”在同一個(gè)時(shí)間點(diǎn)只能被一個(gè)線程持有,而“可重入鎖”可以被單個(gè)線程多次獲取;ReentrantLock又分為“公平鎖”和“非公平鎖”(默認(rèn)),它們的區(qū)別體現(xiàn)在獲取鎖的機(jī)制上:在“公平鎖”的機(jī)制下,線程依次排隊(duì)獲取鎖;而“非公平鎖”機(jī)制下,如果鎖是可獲取狀態(tài),不管自己是不是在隊(duì)列的head節(jié)點(diǎn)都會(huì)去嘗試獲取鎖
內(nèi)部有一個(gè)Sync類繼承自AQS,有兩個(gè)子類FairSync和NonfairSync
1、lock方法:
非公平:調(diào)用NonfairSync中的lock方法;嘗試CAS獲取資源,成功,設(shè)置當(dāng)前線程持有資源;失敗,調(diào)用AQS的acquire方法獲取資源;調(diào)用NonfairSync中的tryAcquire方法嘗試CAS獲取資源(不會(huì)判斷當(dāng)前節(jié)點(diǎn)前是否還有節(jié)點(diǎn),與公平鎖區(qū)別):如果沒(méi)有線程獲取資源,CAS嘗試獲取資源,設(shè)置當(dāng)前線程持有資源,如果當(dāng)前線程已經(jīng)獲取過(guò)資源(可重入),在原來(lái)基礎(chǔ)上+1(不需要CAS,因?yàn)闊o(wú)競(jìng)爭(zhēng)),成功直接返回;失敗則阻塞線程,創(chuàng)建節(jié)點(diǎn),自旋+CAS將節(jié)點(diǎn)放入同步隊(duì)列尾部,當(dāng)前驅(qū)有效節(jié)點(diǎn)是頭節(jié)點(diǎn)時(shí),自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點(diǎn)不為頭節(jié)點(diǎn)時(shí),阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點(diǎn)喚醒;即使當(dāng)前線程中間被中斷過(guò),也可以自旋+CAS嘗試獲取資源);如果自旋+CAS嘗試獲取資源出現(xiàn)異常,要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消
公平:調(diào)用FairSync中的lock方法;調(diào)用AQS的acquire方法獲取資源;調(diào)用FairSync中的tryAcquire方法嘗試CAS獲取資源(要判斷當(dāng)前節(jié)點(diǎn)前是否還有節(jié)點(diǎn),與非公平鎖區(qū)別):如果沒(méi)有線程獲取資源,CAS嘗試獲取資源,設(shè)置當(dāng)前線程持有資源,如果當(dāng)前線程已經(jīng)獲取過(guò)資源(可重入),在原來(lái)基礎(chǔ)上+1(不需要CAS,因?yàn)闊o(wú)競(jìng)爭(zhēng)),成功直接返回;失敗則阻塞線程,創(chuàng)建節(jié)點(diǎn),自旋+CAS將節(jié)點(diǎn)放入同步隊(duì)列尾部,當(dāng)前驅(qū)有效節(jié)點(diǎn)是頭節(jié)點(diǎn)時(shí),自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點(diǎn)不為頭節(jié)點(diǎn)時(shí),阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點(diǎn)喚醒;即使當(dāng)前線程中間被中斷過(guò),也可以自旋+CAS嘗試獲取資源);如果自旋+CAS嘗試獲取資源出現(xiàn)異常,要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消
2、unLock方法:公平和非公平一致;調(diào)用AQS的release方法釋放資源;調(diào)用Sync的tryRelease方法嘗試釋放資源:獲取資源的次數(shù)必須要等于釋放資源的次數(shù),這樣才算是真正釋放了資源,才可以設(shè)置持有資源的線程為空,不需要CAS,因?yàn)闊o(wú)競(jìng)爭(zhēng);喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)(中間會(huì)移除取消狀態(tài)的節(jié)點(diǎn)),后繼節(jié)點(diǎn)自旋+CAS嘗試獲取資源,獲取成功,則從acquire方法返回
3、tryLock方法:調(diào)用Sync的nonfairTryAcquire方法嘗試請(qǐng)求獲取資源(不會(huì)判斷當(dāng)前節(jié)點(diǎn)前是否還有節(jié)點(diǎn),非公平):如果沒(méi)有線程獲取資源,CAS嘗試獲取資源,設(shè)置當(dāng)前線程持有資源,如果當(dāng)前線程已經(jīng)獲取過(guò)資源(可重入),在原來(lái)基礎(chǔ)上+1(不需要CAS,因?yàn)闊o(wú)競(jìng)爭(zhēng)),成功直接返回;失敗則阻塞線程,創(chuàng)建節(jié)點(diǎn),自旋+CAS將節(jié)點(diǎn)放入同步隊(duì)列尾部,當(dāng)前驅(qū)有效節(jié)點(diǎn)是頭節(jié)點(diǎn)時(shí),自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點(diǎn)不為頭節(jié)點(diǎn)時(shí),阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點(diǎn)喚醒;即使當(dāng)前線程中間被中斷過(guò),也可以自旋+CAS嘗試獲取資源);如果自旋+CAS嘗試獲取資源出現(xiàn)異常,要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消
4、等待通知機(jī)制:使用AQS的Condition實(shí)現(xiàn)

JUC-CountDownLatch

CountDownLatch是一個(gè)同步輔助類,是通過(guò)AQS實(shí)現(xiàn)的一個(gè)可重入的共享鎖,可響應(yīng)中斷,會(huì)直接拋出中斷異常;在其他線程完成操作之前,可以有一個(gè)或多個(gè)線程等待;內(nèi)部有一個(gè)Sync類,繼承自AQS,實(shí)現(xiàn)了tryAcquireShared和tryReleaseShared方法
創(chuàng)建CountDownLatch時(shí)會(huì)指定AQS中state大小
1、await方法:調(diào)用AQS的acquireSharedInterruptibly方法獲取共享資源;回調(diào)子類Sync的tryAcquireShared方法嘗試獲取資源(當(dāng)state=0的時(shí)候才可以獲取資源),成功直接返回;失敗則阻塞線程,創(chuàng)建節(jié)點(diǎn),自旋+CAS將節(jié)點(diǎn)放入同步隊(duì)列尾部,當(dāng)前驅(qū)有效節(jié)點(diǎn)是頭節(jié)點(diǎn)時(shí),自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點(diǎn)不為頭節(jié)點(diǎn)時(shí),阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點(diǎn)喚醒;即使當(dāng)前線程中間被中斷過(guò),也可以自旋+CAS嘗試獲取資源),獲取資源成功后,將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),如果還有剩余資源,讓后續(xù)節(jié)點(diǎn)也嘗試獲取資源;如果自旋+CAS嘗試獲取資源出現(xiàn)異常(超時(shí)),要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消;如果中間被中斷,直接拋出中斷異常
2、countDown方法:調(diào)用AQS的releaseShared方法釋放共享資源;回調(diào)子類Sync的tryReleaseShared方法嘗試自旋+CAS釋放資源,喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)(中間會(huì)移除取消狀態(tài)的節(jié)點(diǎn)),后繼節(jié)點(diǎn)自旋+CAS嘗試獲取資源,獲取成功,則從acquireShared方法返回

JUC-ReentrantReadWriteLock

ReentrantReadWriteLock維護(hù)了一對(duì)相關(guān)的鎖:共享鎖readLock和獨(dú)占鎖writeLock
共享鎖readLock用于讀操作,能同時(shí)被多個(gè)線程獲??;獨(dú)占鎖writeLock用于寫入操作,只能被一個(gè)線程持有(支持鎖降級(jí):持有寫鎖的線程可以在寫鎖未釋放之前獲得讀鎖)
Condition只有在寫鎖中用到,讀鎖是不支持Condition的
內(nèi)部有一個(gè)Sync類繼承自AQS,有兩個(gè)子類FairSync和NonfairSync:讀鎖和寫鎖共用一個(gè)狀態(tài),高16位標(biāo)識(shí)讀計(jì)數(shù)了,低16位標(biāo)識(shí)寫重入次數(shù);內(nèi)部有一個(gè)靜態(tài)內(nèi)部類繼承自ThreadLocal用于讀記錄讀線程重入次數(shù)
內(nèi)部有兩個(gè)靜態(tài)內(nèi)部類:ReadLock和WriteLock
1、ReadLock:內(nèi)部有l(wèi)ock和unLock方法,參考CountDownLatch
**lock方法:調(diào)用AQS的acquireShared方法獲取資源;回調(diào)Sync的tryAcquireShared方法嘗試CAS獲取資源(持有寫鎖的線程可以繼續(xù)獲取讀鎖,沒(méi)有線程獲取寫鎖,可以嘗試CAS獲取資源),成功,更新當(dāng)前線程鎖重入次數(shù),直接返回;失敗,則阻塞線程,創(chuàng)建節(jié)點(diǎn),自旋+CAS將節(jié)點(diǎn)放入同步隊(duì)列尾部,當(dāng)前驅(qū)有效節(jié)點(diǎn)是頭節(jié)點(diǎn)時(shí),自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點(diǎn)不為頭節(jié)點(diǎn)時(shí),阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點(diǎn)喚醒;即使當(dāng)前線程中間被中斷過(guò),也可以自旋+CAS嘗試獲取資源),獲取資源成功后,將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),如果還有剩余資源,讓后續(xù)節(jié)點(diǎn)也嘗試獲取資源;如果自旋+CAS嘗試獲取資源出現(xiàn)異常(超時(shí)),要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消;如果中間被中斷,直接拋出中斷異常
**unLock方法:調(diào)用AQS的releaseShared方法釋放共享資源;回調(diào)子類Sync的tryReleaseShared方法嘗試自旋+CAS釋放資源,更新線程重入次數(shù);喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)(中間會(huì)移除取消狀態(tài)的節(jié)點(diǎn)),后繼節(jié)點(diǎn)自旋+CAS嘗試獲取資源,獲取成功,則從acquireShared方法返回
2、WriteLock:內(nèi)部有l(wèi)ock和unLock方法,參考ReentrantLock

線程池

一般不手動(dòng)創(chuàng)建線程,而是使用線程池(降低資源損耗(創(chuàng)建和銷毀線程)、提高響應(yīng)速度、便于管理)
線程池不允許使用 Executors 去創(chuàng)建,而是通過(guò) ThreadPoolExecutor 的方式(規(guī)避資源耗盡的風(fēng)險(xiǎn))
原因:
Executors可以創(chuàng)建3種類型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool
1、FixedThreadPool 和 SingleThreadPool:阻塞隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM
2、CachedThreadPool 和 ScheduledThreadPool:最大線程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線程,從而導(dǎo)致 OOM
線程池調(diào)優(yōu):CPU密集型-CPU核數(shù)+1,IO密集型-CPU*2,使用有界阻塞隊(duì)列

線程池原理

核心組件
1、corePool:核心線程池的大小
2、maximumPool:最大線程池的大小
3、BlockingQueue:用來(lái)暫時(shí)保存任務(wù)的工作隊(duì)列(阻塞隊(duì)列,為什么用阻塞隊(duì)列)
4、RejectedExecutionHandler:當(dāng)ThreadPoolExecutor已經(jīng)關(guān)閉或ThreadPoolExecutor已經(jīng)飽和時(shí)(達(dá)到了最大線程池大小且工作隊(duì)列已滿),execute()方法將要調(diào)用的Handler
5、線程池工廠:可以通過(guò)工廠為線程設(shè)置名字
拒絕策略:
1、AbortPolicy:默認(rèn)策略,在需要拒絕任務(wù)時(shí)拋出RejectedExecutionException
2、CallerRunsPolicy:直接在 execute 方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù),如果線程池已經(jīng)關(guān)閉,任務(wù)將被丟棄
3、DiscardPolicy:直接丟棄任務(wù)
4、DiscardOldestPolicy:丟棄隊(duì)列中等待時(shí)間最長(zhǎng)的任務(wù),并執(zhí)行當(dāng)前提交的任務(wù),如果線程池已經(jīng)關(guān)閉,任務(wù)將被丟棄
5、也可以自定義拒絕策略
原理:(為什么要這樣設(shè)計(jì):避免獲取全局鎖,完成預(yù)熱之后,幾乎都是在執(zhí)行第的二步)
1、向線程池提交任務(wù):execute方法(沒(méi)有返回值)和submit方法(有返回值future,通過(guò)future的get方法可以獲取返回值(阻塞當(dāng)前線程,直到任務(wù)完成,返回結(jié)果))
2、如果當(dāng)前運(yùn)行的線程數(shù)少于corePoolSize,則創(chuàng)建新線程來(lái)執(zhí)行任務(wù),否則向下執(zhí)行(需要獲取全局鎖)
3、如果工作隊(duì)列未滿,將任務(wù)加入工作隊(duì)列,否則向下執(zhí)行
4、如果當(dāng)前運(yùn)行的線程數(shù)少于maximumPoolSize,則創(chuàng)建新線程來(lái)執(zhí)行任務(wù),否則向下執(zhí)行(需要獲取全局鎖)
5、執(zhí)行拒絕策略
6、關(guān)閉線程池:shutdown(推薦)和shutdownNow方法,遍歷線程池中的工作線程,逐個(gè)調(diào)用線程的interrupt方法來(lái)中斷線程(無(wú)法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無(wú)法終止);shutdown中斷的是所有沒(méi)有正在執(zhí)行任務(wù)的線程(還沒(méi)停止),shutdownNow中斷的是所有正在執(zhí)行或暫停任務(wù)的線程(立即停止)
注:線程池創(chuàng)建線程時(shí),會(huì)將線程封裝成工作線程Worker,Worker在執(zhí)行完任務(wù)后,會(huì)循環(huán)從工作隊(duì)列里獲取任務(wù)進(jìn)行執(zhí)行

Future接口與FutureTask實(shí)現(xiàn)類

FutureTask既實(shí)現(xiàn)了Future接口,又實(shí)現(xiàn)了Runnable接口
FutureTask基于AQS實(shí)現(xiàn)
get方法:阻塞當(dāng)前線程(同步隊(duì)列中)直到獲取結(jié)果(類似于acquire方法)
run和cancel方法:改變AQS的狀態(tài),喚醒阻塞線程

Executors

FixedThreadPool:可重用固定線程數(shù)的線程池,適用于為了滿足資源管理的需求,而需要限制當(dāng)前線程數(shù)量的應(yīng)用場(chǎng)景,即負(fù)載比較重的服務(wù)器
1、FixedThreadPool的corePoolSize和maximumPoolSize都被設(shè)置為創(chuàng)建FixedThreadPool時(shí)指定的參數(shù)nThreads
2、當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí),keepAliveTime為多余的空閑線程等待新任務(wù)的最長(zhǎng)時(shí)間,超過(guò)這個(gè)時(shí)間后多余的線程將被終止;這里把keepAliveTime設(shè)置為0L,意味著多余的空閑線程會(huì)被立即終止
3、FixedThreadPool使用無(wú)界隊(duì)列LinkedBlockingQueue作為線程池的工作隊(duì)列(隊(duì)列的容量為Integer.MAX_VALUE)(不會(huì)拒絕任務(wù),可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM)
4、當(dāng)線程池中的線程數(shù)達(dá)到corePoolSize后,新任務(wù)將在無(wú)界隊(duì)列中等待,因此線程池中的線程數(shù)不會(huì)超過(guò)corePoolSize
5、使用無(wú)界隊(duì)列時(shí)maximumPoolSize和keepAliveTime將是效參數(shù)

SingleThreadExecutor:?jiǎn)蝹€(gè)線程(如果內(nèi)部工作線程由于異常而被終止,則會(huì)新建一個(gè)線程替代)的線程池,適用于需要保證順序地執(zhí)行各個(gè)任務(wù),并且在任意時(shí)間點(diǎn),不會(huì)有多個(gè)線程是活動(dòng)的應(yīng)用場(chǎng)景
1、SingleThreadExecutor的corePoolSize和maximumPoolSize被設(shè)置為1
2、其他參數(shù)與FixedThreadPool相同
3、SingleThreadExecutor使用無(wú)界隊(duì)列LinkedBlockingQueue作為線程池的工作隊(duì)列(隊(duì)列的容量為Integer.MAX_VALUE)

CachedThreadPool:大小無(wú)界的線程池,適用于執(zhí)行很多的短期異步任務(wù)的小程序,或者是負(fù)載較輕的服務(wù)器
1、CachedThreadPool的corePoolSize被設(shè)置為0,即corePool為空;maximumPoolSize被設(shè)置為Integer.MAX_VALUE,即maximumPool是無(wú)界的
2、keepAliveTime設(shè)置為60L,意味著CachedThreadPool中的空閑線程等待新任務(wù)的最長(zhǎng)時(shí)間為60秒,空閑線程超過(guò)60秒后將會(huì)被終止
3、CachedThreadPool使用沒(méi)有容量的SynchronousQueue作為線程池的工作隊(duì)列,但CachedThreadPool的maximumPool是無(wú)界的;這意味著,如果主線程提交任務(wù)的速度高于maximumPool中線程處理任務(wù)的速度時(shí),CachedThreadPool會(huì)不斷創(chuàng)建新線程;極端情況下, CachedThreadPool會(huì)因?yàn)閯?chuàng)建過(guò)多線程而耗盡CPU和內(nèi)存資源
5、SynchronousQueue是一個(gè)沒(méi)有容量的阻塞隊(duì)列,每個(gè)插入操作必須等待另一個(gè)線程的對(duì)應(yīng)移除操作,反之亦然
6、CachedThreadPool使用SynchronousQueue,把主線程提交的任務(wù)傳遞給空閑線程執(zhí)行
執(zhí)行流程:
1、執(zhí)行SynchronousQueue.offer(Runnable task);如果當(dāng)前maximumPool中有空閑線程正在執(zhí)行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主線程執(zhí)行 offer操作與空閑線程執(zhí)行的poll操作配對(duì)成功,主線程把任務(wù)交給空閑線程執(zhí)行,execute()方法執(zhí)行完成;否則向下執(zhí)行
2、當(dāng)初始maximumPool為空,或者maximumPool中當(dāng)前沒(méi)有空閑線程時(shí),將沒(méi)有線程執(zhí)行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS);這種情況下,1將失敗,此時(shí)CachedThreadPool會(huì)創(chuàng)建一個(gè)新線程執(zhí)行任務(wù),execute()方法執(zhí)行完成
3、2中新創(chuàng)建的線程將任務(wù)執(zhí)行完后,會(huì)執(zhí)行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS);這個(gè)poll操作會(huì)讓空閑線程最多在SynchronousQueue中等待60秒鐘;如果60秒鐘內(nèi)主線程提交了一個(gè)新任務(wù)(主線程執(zhí)行1)),那么這個(gè)空閑線程將執(zhí)行主線程提交的新任務(wù);否則,這個(gè)空閑線程將終止;由于空閑60秒的空閑線程會(huì)被終止,因此長(zhǎng)時(shí)間保持空閑的CachedThreadPool不會(huì)使用任何資源

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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