JVM-Class類文件結(jié)構(gòu)
常量池:字面量(字符串和final常量)和符號引用(類和接口的全限定名、字段的名稱和描述符刨肃、方法句柄和方法類型古拴、方法的名稱和描述符 )
字段表、方法表真友、屬性表(code屬性存放代碼)
JVM-運(yùn)行時(shí)數(shù)據(jù)區(qū)域
方法區(qū)(線程共享黄痪,可能會(huì)內(nèi)存溢出):用于存儲(chǔ)已被虛擬機(jī)加載的類型信息、常量盔然、靜態(tài)變量桅打、即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)
1、以前使用永久代來實(shí)現(xiàn)方法區(qū)導(dǎo)致Java應(yīng)用更容易遇到內(nèi)存溢出的問題愈案,現(xiàn)在使用本地內(nèi)存來實(shí)現(xiàn)方法區(qū)
2挺尾、JDK 7的HotSpot,已經(jīng)把原本放在永久代的字符串常量池站绪、靜態(tài)變量等移出(移到堆內(nèi))
3遭铺、在JDK 8,完全廢棄了永久代恢准,使用本地內(nèi)存中實(shí)現(xiàn)的元空間來代替魂挂,把JDK 7中永久代還剩余的內(nèi)容(主要是類型信息)全部移到元空間中
4、運(yùn)行時(shí)常量池:Class文件中常量池在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中(也存放直接引用)
堆(線程共享馁筐,可能會(huì)內(nèi)存溢出):
1涂召、存放對象實(shí)例, “幾乎”所有的對象實(shí)例都在這里分配內(nèi)存(由于即時(shí)編譯技術(shù)的進(jìn)步敏沉,尤其是逃逸分析技術(shù)的日漸強(qiáng)大果正,棧上分配、標(biāo)量替換優(yōu)化手段已經(jīng)導(dǎo)致對象可以不在堆內(nèi)分配)
2赦抖、堆中可以劃分出多個(gè)線程私有的分配緩沖區(qū) 舱卡,以更好地回收內(nèi)存,或者更快地分配內(nèi)存
3队萤、通過參數(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ù)庫直接分配堆外內(nèi)存新娜,然后通過一個(gè)存儲(chǔ)在Java堆里面的 DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作,這樣能在一些場景中顯著提高性能既绩,因?yàn)楸苊饬嗽贘ava堆和Native堆中來回復(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í)行完畢的過程,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程
局部變量表(棧幀中笆怠,大小在編譯期已經(jīng)確定铝耻,code屬性中存儲(chǔ)大小):
1蹬刷、用于存放方法參數(shù)和方法內(nèi)部定義的局部變量(基本類型和對象引用)瓢捉、returnAddress 類型(指向了一條字節(jié)碼指令的地址),這些數(shù)據(jù)類型在局部變量表中的存儲(chǔ)空間以局部變量槽來表示
2办成、當(dāng)一個(gè)方法被調(diào)用時(shí)泡态,使用局部變量表來完成實(shí)參到形參的傳遞
3、如果執(zhí)行的是實(shí)例方法(沒有被static修飾的方法)迂卢,局部變量表中第0位存放this引用兽赁,在方法中直接訪問
4、局部變量表中的變量槽是可以重用的冷守;復(fù)用的時(shí)機(jī)是其他變量需要用到這塊變量槽刀崖,因此即使該變量已經(jīng)不被使用,也不會(huì)垃圾回收拍摇,除非賦值null(不建議亮钦,會(huì)被優(yōu)化掉)
5、局部變量不像類變量那樣存在“準(zhǔn)備階段”充活,類變量有兩次賦初始值的過程蜂莉,一次在準(zhǔn)備階段,賦予系統(tǒng)初始值混卵;另外一次在初始化階段映穗,賦予代碼定義的初始值,因此即使在初始化階段代碼沒有為類變量賦值也沒有關(guān)系幕随,類變量仍然具有一個(gè)確定的初始值蚁滋,不會(huì)產(chǎn)生歧義;但局部變量就不一樣了,如果一個(gè)局部變量定義了但沒有賦初始值辕录,那它是完全不能使用的
操作數(shù)棧(棧幀中睦霎,大小在編譯期已經(jīng)確定,code屬性中存儲(chǔ)大凶叩):
1副女、兩個(gè)棧幀會(huì)出現(xiàn)一部分重疊,讓下面棧幀的部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起蚣旱,這樣做不僅節(jié)約了一些空間碑幅,更重要的是在進(jìn)行方法調(diào)用時(shí)就可以直接共用一部分?jǐn)?shù)據(jù),無須進(jìn)行額外的參數(shù)復(fù)制傳遞
動(dòng)態(tài)連接(棧幀中):
1塞绿、每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用枕赵,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接
2、Class文件的常量池中存有大量的符號引用位隶,字節(jié)碼中的方法調(diào)用指令就以常量池里指向方法的符號引用作為參數(shù),這些符號引用一部分會(huì)在類加載階段或者第一次使用的時(shí)候就被轉(zhuǎn)化為直接引用开皿,這種轉(zhuǎn)化被稱為靜態(tài)解析涧黄;另外一部分將在每一次運(yùn)行期間都轉(zhuǎn)化為直接引用,這部分就稱為動(dòng)態(tài)連接
方法返回地址(棧幀中):方法退出時(shí)使用
程序計(jì)數(shù)器(線程私有赋荆,不會(huì)內(nèi)存溢出):字節(jié)碼解釋器工作時(shí)通過改變計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令(線程切換時(shí)為了標(biāo)識每個(gè)線程執(zhí)行位置)
執(zhí)行引擎:
1笋妥、在不同的虛擬機(jī)實(shí)現(xiàn)中,執(zhí)行引擎在執(zhí)行字節(jié)碼的時(shí)候窄潭,通常會(huì)有解釋執(zhí)行(通過解釋器執(zhí)行)和編譯執(zhí)行(通過即時(shí)編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇春宣,也可能兩者兼?zhèn)洌€可能會(huì)有同時(shí)包含幾個(gè)不同級別的即時(shí)編譯器一起工作的執(zhí)行引擎
2嫉你、從外觀上來看月帝,所有的Java虛擬機(jī)的執(zhí)行引擎輸入、輸出都是一致的:輸入的是字節(jié)碼二進(jìn)制流幽污,處理過程是字節(jié)碼解析執(zhí)行的等效過程嚷辅,輸出的是執(zhí)行結(jié)果
方法調(diào)用:
1、方法調(diào)用并不等同于方法中的代碼被執(zhí)行距误,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本 (即調(diào)用哪一個(gè)方法)簸搞,暫時(shí)還未涉及方法內(nèi)部的具體運(yùn)行過程
2、Class文件的編譯過程中不包含傳統(tǒng)程序語言編譯的連接步驟准潭,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號引用趁俊,而不是直接引用
3、解析:所有方法調(diào)用的目標(biāo)方法在Class文件里面都是一個(gè)常量池中的符號引用刑然,在類加載的解析階段寺擂,會(huì)將其中的一部分符號引用轉(zhuǎn)化為直接引用,即調(diào)用目標(biāo)在程序代碼寫好、編譯器進(jìn)行編譯那一刻就已經(jīng)確定下來沽讹,這類方法的調(diào)用被稱為解析
(靜態(tài)方法般卑、私有方法、final方法爽雄、構(gòu)造方法蝠检、父類方法)
4、分派
**靜態(tài)分派:重載
引用類型和實(shí)際類型在程序中都可能會(huì)發(fā)生變化挚瘟,區(qū)別是引用類型的變化僅僅在使用時(shí)發(fā)生叹谁,引用類型不會(huì)被改變,并且最終的引用類型是在編譯期可知的乘盖;而實(shí)際類型變化的結(jié)果在運(yùn)行期才可確定焰檩,編譯器在編譯程序的時(shí)候并不知道一個(gè)對象的實(shí)際類型是什么
// 實(shí)際類型變化
Human human = (new Random()).nextBoolean() ? new Man() : new Woman();
// 引用類型變化
sr.sayHello((Man) human) ;
sr.sayHello((Woman) human);
虛擬機(jī)(或者準(zhǔn)確地說是編譯器)在重載時(shí)是通過參數(shù)的引用類型而不是實(shí)際類型作為判定依據(jù)的,由于引用類型在編譯期可知订框,所以在編譯階段析苫,Javac編譯器就根據(jù)參數(shù)的引用類型決定了會(huì)使用哪個(gè)重載版本
所有依賴引用類型來決定方法執(zhí)行版本的分派動(dòng)作,都稱為靜態(tài)分派穿扳,靜態(tài)分派的最典型應(yīng)用表現(xiàn)就是方法重載衩侥,靜態(tài)分派發(fā)生在編譯階段,因此確定靜態(tài)分派的動(dòng)作實(shí)際上不是由虛擬機(jī)來執(zhí)行的
**動(dòng)態(tài)分派 覆蓋
在運(yùn)行期根據(jù)實(shí)際類型確定方法執(zhí)行版本的分派過程稱為動(dòng)態(tài)分派
**單分派和多分配派 重載+覆蓋
方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量
單分派是根據(jù)一個(gè)宗量對目標(biāo)方法進(jìn)行選擇矛物,多分派則是根據(jù)多于一個(gè)宗量對目標(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)存峦萎,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化忆首,最終形成可以被虛擬機(jī)直接使用的Java類型的過程
在Java語言里面爱榔,類型的加載、連接和初始化過程都是在程序運(yùn)行期間完成的
類的生命周期:加載 糙及、連接(驗(yàn)證搓蚪、準(zhǔn)備、解析)丁鹉、初始化 妒潭、使用和卸載
類加載的時(shí)機(jī)
對于初始化階段,有且只有6種情況必須對類進(jìn)行初始化(主動(dòng)引用)揣钦,其余都是被動(dòng)引用:
1雳灾、new對象、讀寫類型靜態(tài)字段(final常量除外冯凹,編譯期已放入常量池)谎亩、調(diào)用類型靜態(tài)方法時(shí)
2炒嘲、對類型反射調(diào)用時(shí)
3、初始化子類時(shí)匈庭,要先初始化父類
4夫凸、虛擬機(jī)啟動(dòng)時(shí),初始化主類
5阱持、方法句柄對應(yīng)的類初始化
6夭拌、接口定義了默認(rèn)方法,實(shí)現(xiàn)類初始化時(shí)衷咽,要先初始化接口
被動(dòng)引用:
1鸽扁、通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化(對于靜態(tài)字段镶骗, 只有直接定義這個(gè)字段的類才會(huì)被初始化)
2桶现、通過數(shù)組定義來引用類,不會(huì)觸發(fā)此類的初始化(與數(shù)組是不同的類)
3鼎姊、常量在編譯階段會(huì)存入調(diào)用類的常量池中骡和,本質(zhì)上沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化(在編譯階段通過常量傳播優(yōu)化相寇,已經(jīng)將常量的值直接存儲(chǔ)在調(diào)用類的常量池中慰于,以后調(diào)用類對常量的引用,實(shí)際都被轉(zhuǎn)化為對自身常量池的引用了裆赵;也就是說,實(shí)際上調(diào)用類的Class文件之中并沒有原類的符號引用入口跺嗽,這兩個(gè)類在編譯成 Class文件后就已經(jīng)不存在任何聯(lián)系了)
類加載的過程
加載:
1战授、通過類的全限定名獲取定義類的二進(jìn)制字節(jié)流(非數(shù)組類型的加載既可以使用引導(dǎo)類加載器來完成,也可以由用戶自定義的類加載器去完成(重寫一個(gè)類加載器的findClass或loadClass方法)桨嫁;數(shù)組類本身不通過類加載器創(chuàng)建植兰,是由Java虛擬機(jī)直接在內(nèi)存中動(dòng)態(tài)構(gòu)造出來的,但數(shù)組類與類加載器仍然有很密切的關(guān)系璃吧,因?yàn)閿?shù)組類的元素類型最終還是要靠類加載器來完成加載)
2楣导、將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
3、在內(nèi)存中生成代表類的Class對象畜挨,作為方法區(qū)中類的各種數(shù)據(jù)的訪問入口
驗(yàn)證:
1筒繁、文件格式驗(yàn)證
2、元數(shù)據(jù)驗(yàn)證(是否有父類巴元、是否繼承了不該繼承的如final類毡咏、是否實(shí)現(xiàn)了抽象類的所有方法等)
3、字節(jié)碼驗(yàn)證:對類的方法體(Class文件中的code屬性)進(jìn)行檢驗(yàn)分析
4逮刨、符號引用驗(yàn)證:將符號引用轉(zhuǎn)換為直接引用時(shí)驗(yàn)證(解析階段)呕缭,通過全限定名是否能找到對應(yīng)的類等
準(zhǔn)備:
1、準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置初始值(默認(rèn)值)的階段,這些類變量所使用的內(nèi)存都在方法區(qū)(jdk1.8在堆中)中進(jìn)行分配
2恢总、實(shí)例變量將會(huì)在對象實(shí)例化時(shí)隨著對象一起分配在Java堆中
3迎罗、類常量在這個(gè)階段會(huì)被初始化(不是默認(rèn)值)
解析:將常量池內(nèi)的符號引用替換為直接引用
1、符號引用:符號引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān)片仿,引用的目標(biāo)并不一定是已經(jīng)加載到虛擬機(jī)內(nèi)存當(dāng)中的內(nèi)容
2纹安、直接引用:直接引用是可以直接指向目標(biāo)的指針、相對偏移量或者是一個(gè)能間接定位到目標(biāo)的句柄滋戳,直接引用是和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局直接相關(guān)的钻蔑,同一個(gè)符號引用在不同虛擬機(jī)實(shí)例上翻譯出來的直接引用一般不會(huì)相同,如果有了直接引用奸鸯,那引用的目標(biāo)必定已經(jīng)在虛擬機(jī)的內(nèi)存中存在
初始化:
1咪笑、初始化階段就是執(zhí)行類構(gòu)造器方法(所有類變量的賦值動(dòng)作和靜態(tài)語句塊(static{}塊)中的語句,執(zhí)行順序是代碼中的順序)的過程娄涩,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量窗怒,定義在它之后的變量,在前面的靜態(tài)語句塊可以賦值蓄拣,但是不能訪問
2扬虚、父類的類構(gòu)造器方法先執(zhí)行,父類中定義的靜態(tài)語句塊要優(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í)很長的操作张惹,那就可能造成多個(gè)進(jìn)程阻塞舀锨,在實(shí)際應(yīng)用中這種阻塞往往是很隱蔽的
4、同一個(gè)類加載器下宛逗,一個(gè)類型只會(huì)被初始化一次
類加載器
1坎匿、類加載器通過一個(gè)類的全限定名來獲取描述該類的二進(jìn)制字節(jié)流
2、對于任意一個(gè)類雷激,都必須由加載它的類加載器和這個(gè)類本身一起共同確立其在Java虛擬機(jī)中的唯一性替蔬,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間屎暇,即比較兩個(gè)類是否“相等”进栽,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義,否則恭垦,即使這兩個(gè)類來源于同一個(gè) Class文件快毛,被同一個(gè)Java虛擬機(jī)加載格嗅,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等
雙親委派模型
1唠帝、Java一直保持著三層類加載器屯掖、雙親委派的類加載架構(gòu)
2、三層類加載器:
**啟動(dòng)類加載器:這個(gè)類加載器負(fù)責(zé)加載存放在 <JAVA_HOME>\lib目錄中的類襟衰,啟動(dòng)類加載器無法被Java程序直接引用贴铜,用戶在編寫自定義類加載器時(shí), 如果需要把加載請求委派給啟動(dòng)類加載器去處理瀑晒,那直接使用null代替即可
**擴(kuò)展類加載器:這個(gè)類加載器負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的類绍坝,由于擴(kuò)展類加載器是由Java代碼實(shí)現(xiàn)的,開發(fā)時(shí)可以直接在程序中使用擴(kuò)展類加載器來加載Class文件
**應(yīng)用程序類加載器:這個(gè)類加載器負(fù)責(zé)加載用戶類路徑(ClassPath)上所有的類庫苔悦,開發(fā)時(shí)同樣可以直接在代碼中使用這個(gè)類加載器轩褐,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器
3玖详、各種類加載器之間的層次關(guān)系被稱為類加載器的雙親委派模型把介,雙親委派模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)有自己的父類加載器蟋座,不過這里類加載器之間的父子關(guān)系一般不是以繼承的關(guān)系來實(shí)現(xiàn)的拗踢,而是通常使用組合關(guān)系來復(fù)用父類加載器的代碼(高內(nèi)聚,低耦合)
4向臀、雙親委派模型的工作流程:如果一個(gè)類加載器收到了類加載的請求巢墅,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請求委派給父類加載器去完成券膀,每一個(gè)層次的類加載器都是如此君纫,因此所有的加載請求最終都應(yīng)該傳送到最頂層的啟動(dòng)類加載器中,只有當(dāng)父類加載器反饋?zhàn)约簾o法完成這個(gè)加載請求(它的搜索范圍中沒有找到所需的類)時(shí)三娩,子類加載器才會(huì)嘗試自己去完成加載
5庵芭、使用雙親委派模型來組織類加載器之間的關(guān)系妹懒,一個(gè)顯而易見的好處就是Java中的類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系雀监,如類java.lang.Object,無論哪一個(gè)類加載器要加載這個(gè)類眨唬,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載会前,因此Object類在程序的各種類加載器環(huán)境中都能夠保證是同一個(gè)類,反之匾竿,如果沒有使用雙親委派模型瓦宜,都由各個(gè)類加載器自行去加載的話,如果用戶自己也編寫了一個(gè)名為java.lang.Object的類岭妖,并放在程序的 ClassPath中临庇,那系統(tǒng)中就會(huì)出現(xiàn)多個(gè)不同的Object類反璃,Java類型體系中最基礎(chǔ)的行為也就無從保證,應(yīng)用程序?qū)?huì)變得一片混亂假夺,如果寫一個(gè)與rt.jar類庫中已有類重名的Java 類淮蜈,將會(huì)發(fā)現(xiàn)它可以正常編譯,但永遠(yuǎn)無法被加載運(yùn)行
6已卷、雙親委派模型實(shí)現(xiàn):先檢查請求加載的類型是否已經(jīng)被加載過梧田,若沒有則調(diào)用父加載器的 loadClass()方法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器侧蘸,如果父類加載器加載失敗裁眯, 拋出異常,才調(diào)用自己的findClass()方法嘗試進(jìn)行加載
破壞雙親委派模型
1讳癌、按照loadClass()方法的邏輯穿稳,如果父類加載失敗,會(huì)自動(dòng)調(diào)用自己的findClass()方法來完成加載析桥,這樣既不影響用戶按照自己的意愿去加載類司草,又可以保證新寫出來的類加載器是符合雙親委派規(guī)則的
2、雙親委派很好地解決了各個(gè)類加載器協(xié)作時(shí)基礎(chǔ)類型的一致性問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載)泡仗,基礎(chǔ)類型之所以被稱為“基礎(chǔ)”埋虹,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a繼承、調(diào)用的API存在娩怎,但程序設(shè)計(jì)往往沒有絕對不變的完美規(guī)則搔课,如果有基礎(chǔ)類型又要調(diào)用回用戶的代碼,那該怎么辦呢截亦?如JDBC使用線程上下文類加載器去加載所需的代碼爬泥,這是一種父類加載器去請求子類加載器完成類加載的行為,這種行為實(shí)際上是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器崩瓤,已經(jīng)違背了雙親委派模型的一般性原則袍啡,但也是無可奈何的事情
3、由于用戶對程序動(dòng)態(tài)性(如代碼熱替換却桶、模塊熱部署)的追求境输,需要破壞雙親委派模型;OSGi實(shí)現(xiàn)模塊化熱部署的關(guān)鍵是它自定義的類加載器機(jī)制的實(shí)現(xiàn)颖系,每一個(gè)程序模塊都有一個(gè)自己的類加載器嗅剖,當(dāng)需要更換一個(gè)程序模塊,就把程序模塊連同類加載器一起換掉以實(shí)現(xiàn)代碼的熱替換
JVM-對象
對象的創(chuàng)建
1嘁扼、校驗(yàn)是否能在常量池中定位到一個(gè)類的符號引用
2信粮、校驗(yàn)符號引用代表的類是否已被加載、解析和初始化過趁啸,如果沒有强缘,必須先執(zhí)行相應(yīng)的類加載過程
3督惰、為新生對象分配內(nèi)存,對象所需內(nèi)存的大小在類加載完成后便可完全確定旅掂,使用指針碰撞(連續(xù)分配)或空閑列表(記錄可用空閑內(nèi)存)來分配內(nèi)存(需要處理并發(fā)情況下線程安全問題姑丑,對分配內(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)存空間(但不包括對象頭)都初始化為零值称龙,保證對象的實(shí)例變量可以不賦初始值就可以直接使用
5留拾、設(shè)置對象頭信息疏尿,如這個(gè)對象是哪個(gè)類的實(shí)例贡羔、如何才能找到類的元數(shù)據(jù)信息杀狡、對象的哈希碼(真正調(diào)用Object::hashCode方法時(shí)才計(jì)算)坯沪、對象的GC分代年齡等信息;根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同襟雷,如是否啟用偏向鎖等端三,對象頭會(huì)有不同的設(shè)置方式
6锯茄、執(zhí)行構(gòu)造函數(shù)搔驼,初始化實(shí)例變量
對象的內(nèi)存結(jié)構(gòu)
對象頭谈火、實(shí)例數(shù)據(jù)、對齊填充
1舌涨、對象頭:包括兩類信息糯耍,第一類是用于存儲(chǔ)對象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼囊嘉、GC分代年齡温技、鎖狀態(tài)標(biāo)志、線程持有的鎖扭粱、偏向線程ID舵鳞、偏向時(shí)間戳等,即Mark Word琢蛤,是一個(gè)動(dòng)態(tài)定義的數(shù)據(jù)結(jié)構(gòu)
第二類是類型指針(如果是句柄就不需要)蜓堕,即對象指向它的類型元數(shù)據(jù)的指針,通過這個(gè)指針可以確定該對象是哪個(gè)類的實(shí)例
此外虐块,如果對象是一個(gè)Java數(shù)組俩滥,在對象頭中還必須有一塊用于記錄數(shù)組長度的數(shù)據(jù)
2嘉蕾、實(shí)例數(shù)據(jù):無論是從父類繼承下來的贺奠,還是在子類中定義的實(shí)例變量(不包括靜態(tài)變量)都必須記錄起來
對象的訪問定位
通過棧上的reference(句柄和直接指針)數(shù)據(jù)來操作堆上的具體對象
1、句柄(易于維護(hù)):Java堆中將可能會(huì)劃分出一塊內(nèi)存來作為句柄池错忱,reference中存儲(chǔ)的就是對象的句柄地址儡率,而句柄中包含了對象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自具體的地址信息
2挂据、直接指針(速度快,HotSpot使用):在Mark Word中存放類型指針去訪問類型數(shù)據(jù)的相關(guān)信息儿普,reference中存儲(chǔ)的直接就是對象地址崎逃,如果只是訪問對象本身的話,就不需要多一次間接訪問的開銷
JVM-垃圾收集
哪些內(nèi)存需要回收眉孩? Java堆和方法區(qū)
方法區(qū)的垃圾收集:廢棄的常量和不再使用的類型
判定一個(gè)類型是否屬于“不再被使用的類”:
1个绍、該類所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實(shí)例
2浪汪、加載該類的類加載器已經(jīng)被回收
3巴柿、該類對應(yīng)的Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法
什么時(shí)候回收死遭?
引用計(jì)數(shù)算法
在對象中添加一個(gè)引用計(jì)數(shù)器广恢,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加一呀潭;當(dāng)引用失效時(shí)钉迷,計(jì)數(shù)器值就減一;任何時(shí)刻計(jì)數(shù)器為零的對象就是不可能再被使用的
很難解決對象之間相互循環(huán)引用的問題
可達(dá)性分析算法
通過一系列稱為“GC Roots”的根對象作為起始節(jié)點(diǎn)集钠署,根據(jù)引用關(guān)系連接其他對象糠聪,如果某個(gè)對象到GC Roots間不可達(dá),則對象是不可能再被使用的
根對象:
1谐鼎、在虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象枷颊,如參數(shù)、局部變量该面、臨時(shí)變量
2夭苗、在方法區(qū)中類變量引用的對象
3、在方法區(qū)中常量引用的對象
4隔缀、在本地方法棧中Native方法引用的對象
5题造、Java虛擬機(jī)內(nèi)部的引用,如基本數(shù)據(jù)類型對應(yīng)的Class對象猾瘸,一些常駐的異常對象等界赔,還有系統(tǒng)類加載器
6、所有被同步鎖(synchronized關(guān)鍵字)持有的對象
某個(gè)區(qū)域里的對象完全有可能被位于堆中其他區(qū)域的對象所引用牵触,這時(shí)候就需要將這些關(guān)聯(lián)區(qū)域的對象也一并加入GC Roots集合中去淮悼,才能保證可達(dá)性分析的正確性
引用:
1、強(qiáng)引用揽思,只要強(qiáng)引用關(guān)系還存在袜腥,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對象
2、軟引用钉汗,只被軟引用關(guān)聯(lián)著的對象羹令,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前鲤屡,會(huì)把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存福侈, 才會(huì)拋出內(nèi)存溢出異常
3酒来、弱引用,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生為止肪凛,當(dāng)垃圾收集器開始工作堰汉,無論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對象
4伟墙、虛引用衡奥,一個(gè)對象是否有虛引用的存在,完全不會(huì)對其生存時(shí)間構(gòu)成影響远荠,也無法通過虛引用來取得一個(gè)對象實(shí)例
如何回收矮固?分代收集理論
分代收集理論:
1、弱分代假說:絕大多數(shù)對象都是朝生夕滅的
2譬淳、強(qiáng)分代假說:熬過越多次垃圾收集過程的對象就越難以消亡
3档址、跨代引用假說:跨代引用相對于同代引用來說僅占極少數(shù)(針對對象不是孤立的,對象之間會(huì)存在跨代引用)(在新生代上建立一個(gè)全局的數(shù)據(jù)結(jié)構(gòu)即“記憶集”邻梆,把老年代劃分成若干小塊守伸,標(biāo)識出老年代的哪一塊內(nèi)存會(huì)存在跨代引用;當(dāng)發(fā)生Minor GC時(shí)浦妄,只有包含了跨代引用的小塊內(nèi)存里的對象才會(huì)被加入到GC Roots進(jìn)行掃描尼摹;雖然這種方法需要在對象改變引用關(guān)系(如將自己或者某個(gè)屬性賦值)時(shí)維護(hù)記錄數(shù)據(jù)的正確性,會(huì)增加一些運(yùn)行時(shí)的開銷剂娄,但比起收集時(shí)掃描整個(gè)老年代來說仍然是劃算的)
新生代收集(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)記出所有(不)需要回收的對象和二,在標(biāo)記完成后,統(tǒng)一回收掉所有被標(biāo)記的對象耳胎,也可以反過來惯吕,標(biāo)記存活的對象,統(tǒng)一回收所有未被標(biāo)記的對象
缺點(diǎn):
1怕午、執(zhí)行效率不穩(wěn)定废登,如果Java堆中包含大量對象,而且其中大部分是需要被回收的郁惜,這時(shí)必須進(jìn)行大量標(biāo)記和清除的動(dòng)作堡距,導(dǎo)致標(biāo)記和清除兩個(gè)過程的執(zhí)行效率都隨對象數(shù)量增長而降低
2、內(nèi)存空間碎片問題,標(biāo)記吏颖、清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致當(dāng)以后在程序運(yùn)行過程中需要分配較大對象時(shí)無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作恨樟,分配時(shí)需要使用空閑分配鏈表
標(biāo)記-復(fù)制算法(新生代)
將可用內(nèi)存按容量劃分為大小相等的兩塊半醉,每次只使用其中的一塊,當(dāng)這一塊的內(nèi)存用完了劝术,就將還存活著的對象復(fù)制到另外一塊上面缩多,然后再把已使用過的內(nèi)存空間一次清理掉
如果內(nèi)存中多數(shù)對象都是存活的,這種算法將會(huì)產(chǎn)生大量的內(nèi)存間復(fù)制的開銷养晋,但對于多數(shù)對象都是可回收的情況衬吆,算法需要復(fù)制的就是占少數(shù)的存活對象,而且每次都是針對整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收绳泉,分配內(nèi)存時(shí)也就不用考慮有空間碎片的復(fù)雜情況逊抡,只要移動(dòng)堆頂指針,按順序分配即可
這種復(fù)制回收算法的代價(jià)是將可用內(nèi)存縮小為了原來的一半零酪,空間浪費(fèi)
改進(jìn):把新生代分為一塊較大的Eden空間和兩塊較小的 Survivor空間冒嫡,每次分配內(nèi)存只使用Eden和其中一塊Survivor,發(fā)生垃圾收集時(shí)四苇,將Eden和Survivor中仍然存活的對象一次性復(fù)制到另外一塊Survivor空間上孝凌,然后直接清理掉Eden和已用過的那塊Survivor空間
需要依賴其他內(nèi)存區(qū)域(老年代)進(jìn)行分配擔(dān)保
標(biāo)記-整理算法(老年代)
標(biāo)記完對象后,不是直接對可回收對象進(jìn)行清理月腋,而是讓所有存活的對象都向內(nèi)存空間一端移動(dòng)蟀架,然后直接清理掉邊界以外的內(nèi)存
存活對象太多,移動(dòng)負(fù)擔(dān)重榆骚,對象移動(dòng)操作必須全程暫停用戶應(yīng)用程序才能進(jìn)行
從垃圾收集的停頓時(shí)間來看片拍,不移動(dòng)對象停頓時(shí)間會(huì)更短,甚至可以不需要停頓妓肢,但是從整個(gè)程序的吞吐量來看穆碎,移動(dòng)對象會(huì)更劃算
CMS收集器:虛擬機(jī)平時(shí)多數(shù)時(shí)間都采用標(biāo)記-清除算法,暫時(shí)容忍內(nèi)存碎片的存在职恳,直到內(nèi)存空間的碎片化程度已經(jīng)大到影響對象分配時(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í)最長的查找引用鏈的過程已經(jīng)可以做到與用戶線程一起并發(fā)
3操禀、在HotSpot中褂策,使用OopMap的數(shù)據(jù)結(jié)構(gòu)記錄哪些地方存放著對象引用,HotSpot并不是為每一條指令都生成對應(yīng)的OopMap(消耗內(nèi)存空間),而是在安全點(diǎn)(“長時(shí)間執(zhí)行”的地方斤寂,如方法調(diào)用耿焊、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等都屬于指令序列復(fù)用遍搞,程序會(huì)檢查垃圾收集標(biāo)志位罗侯,如果需要垃圾收集,主動(dòng)停頓在這里溪猿,等待垃圾收集)钩杰;但是并不是所有程序都能執(zhí)行到安全點(diǎn),如程序“不執(zhí)行”(就是沒有分配處理器時(shí)間诊县,如用戶線程處于Sleep狀態(tài)或者Blocked狀態(tài))的時(shí)候線程無法響應(yīng)虛擬機(jī)的中斷請求讲弄,就不能再走到安全點(diǎn)去中斷掛起自己,虛擬機(jī)也顯然不可能持續(xù)等待線程被喚醒重新獲取時(shí)間片依痊,再到安全點(diǎn)避除,因此把這行場景設(shè)置為安全區(qū)域,當(dāng)程序執(zhí)行到這些區(qū)域時(shí)胸嘁,會(huì)標(biāo)識自己已經(jīng)進(jìn)入了安全區(qū)域驹饺,當(dāng)這段時(shí)間里虛擬機(jī)要發(fā)起垃圾收集時(shí)就不必去管這些已聲明自己在安全區(qū)域內(nèi)的線程,當(dāng)程序要離開安全區(qū)域時(shí)缴渊,它要檢查虛擬機(jī)是否已經(jīng)完成了根節(jié)點(diǎn)枚舉(或者垃圾收集過程中其他需要暫停用戶線程的階段)赏壹,如果完成了,那程序就繼續(xù)執(zhí)行衔沼;否則它就必須一直等待蝌借,直到收到可以離開安全區(qū)域的信號為止
4、跨代引用問題:記憶集是一種用于記錄從非收集區(qū)域指向收集區(qū)域的指針集合的抽象數(shù)據(jù)結(jié)構(gòu)(解決跨代引用的問題)指蚁,最常用的實(shí)現(xiàn)形式是卡表(每個(gè)記錄精確到一塊內(nèi)存區(qū)域菩佑,該區(qū)域內(nèi)有對象含有跨代指針)(寫屏障,類似于切面)
3凝化、并發(fā)的可達(dá)性分析:如果用戶線程與收集器是并發(fā)工作稍坯,這樣可能出現(xiàn)兩種后果:一種是把原本消亡的對象錯(cuò)誤標(biāo)記為存活, 只不過產(chǎn)生了一點(diǎn)逃過本次收集的浮動(dòng)垃圾而已搓劫,下次收集清理掉就好瞧哟;另一種是把原本存活的對象錯(cuò)誤標(biāo)記為已消亡,非常致命
4枪向、當(dāng)且僅當(dāng)以下兩個(gè)條件同時(shí)滿足時(shí)勤揩,會(huì)產(chǎn)生“對象消失”的問題: 賦值器插入了一條或多條從標(biāo)記對象到未標(biāo)記對象的新引用; 賦值器刪除了全部從標(biāo)記中對象到該未標(biāo)記對象的直接或間接引用(未標(biāo)記的對象與標(biāo)記對象新建立了引用關(guān)系)
要解決并發(fā)掃描時(shí)的對象消失問題秘蛔,只需破壞這兩個(gè)條件的任意一個(gè)即可
兩種解決方案:
1陨亡、增量更新要破壞的是第一個(gè)條件傍衡,當(dāng)標(biāo)記對象插入新的指向未標(biāo)記對象的引用關(guān)系時(shí),就將這個(gè)新插入的引用記錄下來负蠕,等并發(fā)掃描結(jié)束之后蛙埂,再將這些記錄過的引用關(guān)系中的標(biāo)記對象為根,重新掃描一次(CMS)
2遮糖、原始快照要破壞的是第二個(gè)條件绣的,重新掃描一次指向刪除對象所有標(biāo)記對象(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)到的對象谈山,速度很快
2俄删、并發(fā)標(biāo)記:從GC Roots的直接關(guān)聯(lián)對象開始遍歷整個(gè)對象圖的過程,這個(gè)過程耗時(shí)較長但是不需要停頓用戶線程奏路,可以與垃圾收集線程一起并發(fā)運(yùn)行
3畴椰、重新標(biāo)記:需要停頓時(shí)間,是為了修正并發(fā)標(biāo)記期間鸽粉,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對象的標(biāo)記記錄(增量更新)斜脂,這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短
4触机、并發(fā)清除:清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象帚戳,由于不需要移動(dòng)存活對象,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的
從總體上來說儡首,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的
缺點(diǎn):
1片任、對處理器資源非常敏感,在并發(fā)階段蔬胯,它雖然不會(huì)導(dǎo)致用戶線程停頓对供,但卻會(huì)因?yàn)檎加昧艘徊糠志€程(或者說處理器的計(jì)算能力)而導(dǎo)致應(yīng)用程序變慢,降低總吞吐量
2氛濒、無法處理“浮動(dòng)垃圾”产场,有可能出現(xiàn)并發(fā)失敗進(jìn)而導(dǎo)致另一次完全停頓線程的Full GC的產(chǎn)生
**在CMS的并發(fā)標(biāo)記和并發(fā)清理階段,用戶線程是還在繼續(xù)運(yùn)行的舞竿,程序在運(yùn)行自然就還會(huì)伴隨有新的垃圾對象不斷產(chǎn)生涝动,但這一部分垃圾對象是出現(xiàn)在標(biāo)記過程結(jié)束以后,CMS無法在當(dāng)次收集中處理掉它們炬灭,只好留待下一次垃圾收集時(shí)再清理掉
**同樣也是由于在垃圾收集階段用戶線程還需要持續(xù)運(yùn)行醋粟,那就還需要預(yù)留足夠內(nèi)存空間提供給用戶線程使用靡菇,因此CMS收集器不能像其他收集器那樣等待到老年代幾乎完全被填滿了再進(jìn)行收集,必須預(yù)留一部分空間供并發(fā)收集時(shí)的程序運(yùn)作使用
**要是CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序分配新對象的需要米愿,就會(huì)出現(xiàn)一次并發(fā)失敗厦凤,這時(shí)候虛擬機(jī)將不得不啟動(dòng)后備預(yù)案:凍結(jié)用戶線程的執(zhí)行,臨時(shí)啟用Serial Old收集器來重新進(jìn)行老年代的垃圾收集育苟, 但這樣停頓時(shí)間就很長了
3较鼓、收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生,空間碎片過多時(shí)违柏,將會(huì)給大對象分配帶來很大麻煩博烂,往往會(huì)出現(xiàn)老年代還有很多剩余空間,但就是無法找到足夠大的連續(xù)空間來分配當(dāng)前對象漱竖,而不得不提前觸發(fā)一次Full GC的情況
G1收集器 (全堆)面向局部收集和基于Region(大小相等)的內(nèi)存布局禽篱,可以面向堆內(nèi)存任何部分來組成回收集進(jìn)行回收,Region可以作為新生代馍惹、老年代躺率,不同階段可以有不同的角色,G1收集器之所以能建立可預(yù)測的停頓時(shí)間模型万矾,是因?yàn)樗鼘egion作為單次回收的最小單元悼吱,即每次收集到的內(nèi)存空間都是Region大小的整數(shù)倍
實(shí)現(xiàn)中的關(guān)鍵問題:
1、將Java堆分成多個(gè)獨(dú)立Region后良狈,Region里面存在的跨Region引用對象如何解決后添?使用記憶集避免全堆作為GC Roots掃描,每個(gè)Region都維護(hù)有自己的記憶集(浪費(fèi)內(nèi)存空間)薪丁,這些記憶集會(huì)記錄下別的Region 指向自己的指針遇西,并標(biāo)記這些指針分別在哪些內(nèi)存區(qū)域
2、在并發(fā)標(biāo)記階段如何保證收集線程與用戶線程互不干擾地運(yùn)行窥突?原始快照+指針(隔開空閑空間和待收集空間)
流程:
1努溃、初始標(biāo)記:僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,并且修改TAMS 指針的值阻问,讓下一階段用戶線程并發(fā)運(yùn)行時(shí)梧税,能正確地在可用的Region中分配新對象,這個(gè)階段需要停頓線程称近,但耗時(shí)很短第队,而且是借用進(jìn)行Minor GC的時(shí)候同步完成的,所以G1收集器在這個(gè)階段實(shí)際并沒有額外的停頓
2刨秆、并發(fā)標(biāo)記:從GC Root開始對堆中對象進(jìn)行可達(dá)性分析凳谦,遞歸掃描整個(gè)堆里的對象圖,找出要回收的對象衡未,這階段耗時(shí)較長尸执,但可與用戶程序并發(fā)執(zhí)行家凯,當(dāng)對象圖掃描完成以后,還要重新處理SATB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對象(原始快照)
3如失、最終標(biāo)記:對用戶線程做另一個(gè)短暫的暫停绊诲,用于處理并發(fā)階段結(jié)束后仍遺留下來的最后那少量的SATB記錄
4、篩選回收:負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù)褪贵,對各個(gè)Region的回收價(jià)值和成本進(jìn)行排序掂之,根據(jù)用戶所期望的停頓時(shí)間來制定回收計(jì)劃,可以自由選擇任意多個(gè)Region 構(gòu)成回收集脆丁,然后把決定回收的那一部分Region的存活對象復(fù)制到空的Region中世舰,再清理掉整個(gè)舊Region的全部空間,這里的操作涉及存活對象的移動(dòng)槽卫,是必須暫停用戶線程跟压,由多條收集器線程并行完成的
G1從整體來看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器,但從局部(兩個(gè)Region 之間)上看又是基于“標(biāo)記-復(fù)制”算法實(shí)現(xiàn)晒夹,無論如何裆馒,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片
缺點(diǎn):
1姊氓、在用戶程序運(yùn)行過程中丐怯,G1無論是為了垃圾收集產(chǎn)生的內(nèi)存占用還是程序運(yùn)行時(shí)的額外執(zhí)行負(fù)載都要比CMS要高
2、就內(nèi)存占用來說翔横,雖然G1和CMS都使用卡表來處理跨代指針读跷,但G1的卡表實(shí)現(xiàn)更為復(fù)雜,而且堆中每個(gè)Region禾唁,無論扮演的是新生代還是老年代角色效览,都必須有一份卡表,這導(dǎo)致G1的記憶集(和其他內(nèi)存消耗)占用更多的內(nèi)存空間荡短;相比起來CMS的卡表就相當(dāng)簡單丐枉, 只有唯一一份,而且只需要處理老年代到新生代的引用掘托,反過來則不需要瘦锹,由于新生代的對象具有朝生夕滅的不穩(wěn)定性,引用變化頻繁闪盔,能省下這個(gè)區(qū)域的維護(hù)開銷是很劃算的
3弯院、在執(zhí)行負(fù)載的角度上,同樣由于兩個(gè)收集器各自的細(xì)節(jié)實(shí)現(xiàn)特點(diǎn)導(dǎo)致了用戶程序運(yùn)行時(shí)的負(fù)載會(huì)有不同
**如它們都使用到寫屏障泪掀,CMS用寫后屏障來更新維護(hù)卡表听绳;而G1除了使用寫后屏障來進(jìn)行同樣的卡表維護(hù)操作外,為了實(shí)現(xiàn)原始快照搜索 (SATB)算法异赫,還需要使用寫前屏障來跟蹤并發(fā)時(shí)的指針變化情況
**相比起增量更新算法椅挣,原始快照搜索能夠減少并發(fā)標(biāo)記和重新標(biāo)記階段的消耗头岔,避免CMS那樣在最終標(biāo)記階段停頓時(shí)間過長的缺點(diǎn), 但是在用戶程序運(yùn)行過程中確實(shí)會(huì)產(chǎn)生由跟蹤引用變化帶來的額外負(fù)擔(dān)鼠证,由于G1對寫屏障的復(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)記的對象變多而導(dǎo)致停頓時(shí)間隨之增長弯予,但是對于標(biāo)記階段之后的處理戚宦,仍未得到妥善解決,CMS使用標(biāo)記-清除算法锈嫩,雖然避免了整理階段收集器帶來的停頓受楼,但是清除算法不論如何優(yōu)化改進(jìn),在設(shè)計(jì)原理上避免不了空間碎片的產(chǎn)生呼寸,隨著空間碎片不斷淤積最終依然逃不過停頓線程的命運(yùn)艳汽;G1雖然可以按更小的粒度進(jìn)行回收,從而抑制整理階段出現(xiàn)時(shí)間過長的停頓对雪,但畢竟也還是要暫停的
ZGC收集器 (全堆)
幾乎整個(gè)工作過程全部都是并發(fā)的河狐,只有初始標(biāo)記、最終標(biāo)記這些階段有短暫的停頓瑟捣,這部分停頓的時(shí)間基本上是固定的馋艺,與堆的容量、堆中對象的數(shù)量沒有正比例關(guān)系
ZGC收集器是一款基于Region內(nèi)存布局的迈套,(暫時(shí)) 不設(shè)分代的捐祠,使用了讀屏障、染色指針和內(nèi)存多重映射等技術(shù)來實(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è)大對象靴寂,實(shí)際容量完全有可能小于中型Region,大型Region在ZGC的實(shí)現(xiàn)中是不會(huì)被重分配的召耘,因?yàn)閺?fù)制一個(gè)大對象的代價(jià)非常高昂
并發(fā)整理算法的實(shí)現(xiàn)(染色指針技術(shù)):
1百炬、從前,如果我們要在對象上存儲(chǔ)一些額外的污它、只供收集器或者虛擬機(jī)本身使用的數(shù)據(jù)剖踊,通常會(huì)在對象頭中增加額外的存儲(chǔ)字段缭嫡,如對象的哈希碼以蕴、分代年齡、鎖記錄等就是這樣存儲(chǔ)的译隘,這種記錄方式在有對象訪問的場景下是很自然流暢的固惯,不會(huì)有什么額外負(fù)擔(dān)
2梆造、但如果對象存在被移動(dòng)過的可能性,即不能保證對象訪問能夠成功呢葬毫? 又或者有一些根本就不會(huì)去訪問對象镇辉,但又希望得知該對象的某些信息的應(yīng)用場景呢?能不能從指針或者與對象內(nèi)存無關(guān)的地方得到這些信息贴捡,如是否能夠看出來對象被移動(dòng)過忽肛?追蹤式收集算法的標(biāo)記階段就可能存在只跟指針打交道而不必涉及指針?biāo)玫膶ο蟊旧淼膱鼍埃鐚ο髽?biāo)記的過程中需要給對象打上三色標(biāo)記栈暇,這些標(biāo)記本質(zhì)上就只和對象的引用有關(guān)麻裁,而與對象本身無關(guān)——某個(gè)對象只有它的引用關(guān)系能決定它存活與否箍镜,對象上其他所有的屬性都不能夠影響它的存活判定結(jié)果
3源祈、HotSpot虛擬機(jī)的幾種收集器有不同的標(biāo)記實(shí)現(xiàn)方案,有的把標(biāo)記直接記錄在對象頭上(如Serial收集器)色迂,有的把標(biāo)記記錄在與對象相互獨(dú)立的數(shù)據(jù)結(jié)構(gòu)上(如G1用稱為BitMap的結(jié)構(gòu)來記錄標(biāo)記信息)香缺,而ZGC的染色指針直接把標(biāo)記信息記在引用對象的指針上
4、通過指針上的標(biāo)志位歇僧,虛擬機(jī)可以直接從指針中看到其引用對象的三色標(biāo)記狀態(tài)图张、是否進(jìn)入了重分配集(即被移動(dòng)過)、是否只能通過finalize()方法才能被訪問到
優(yōu)點(diǎn):
**染色指針可以使得一旦某個(gè)Region的存活對象被移走之后诈悍,這個(gè)Region立即就能夠被釋放和重用掉祸轮,而不必等待整個(gè)堆中所有指向該Region的引用都被修正后才能清理;使得理論上只要還有一個(gè)空閑Region侥钳,ZGC就能完成收集
**染色指針可以大幅減少在垃圾收集過程中內(nèi)存屏障的使用數(shù)量适袜,設(shè)置內(nèi)存屏障,尤其是寫屏障的目的通常是為了記錄對象引用的變動(dòng)情況舷夺,如果將這些信息直接維護(hù)在指針中苦酱,顯然就可以省去一些專門的記錄操作售貌,實(shí)際上,到目前為止ZGC都并未使用任何寫屏障疫萤,只使用了讀屏障(一部分是染色指針的功勞颂跨,一部分是ZGC現(xiàn)在還不支持分代收集,天然就沒有跨代引用的問題)扯饶,所以ZGC對吞吐量的影響也相對較低
**染色指針可以作為一種可擴(kuò)展的存儲(chǔ)結(jié)構(gòu)用來記錄更多與對象標(biāo)記恒削、重定位過程相關(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)存地址上斑粱,這是一種多對一映射,意味著ZGC在虛擬內(nèi)存中看到的地址空間要比實(shí)際的堆內(nèi)存容量來得更大脯爪,把染色指針中的標(biāo)志位看作是地址的分段符则北,那只要將這些不同的地址段都映射到同一個(gè)物理內(nèi)存空間,經(jīng)過多重映射轉(zhuǎn)換后痕慢,就可以使用染色指針正常進(jìn)行尋址了
流程:
1尚揣、并發(fā)標(biāo)記:并發(fā)標(biāo)記是遍歷對象圖做可達(dá)性分析的階段,前后也要經(jīng)過初始標(biāo)記掖举、最終標(biāo)記的短暫停頓快骗,ZGC 的標(biāo)記是在指針上而不是在對象上進(jìn)行的,標(biāo)記階段會(huì)更新染色指針中的標(biāo)志位
2塔次、并發(fā)預(yù)備重分配:這個(gè)階段需要根據(jù)特定的查詢條件統(tǒng)計(jì)得出本次收集過程要清理哪些Region方篮,將這些Region組成重分配集,ZGC劃分Region的目的并非為了像G1那樣做收益優(yōu)先的增量回收励负,相反藕溅,ZGC每次回收都會(huì)掃描所有的Region,用范圍更大的掃描成本換取省去G1中記憶集的維護(hù)成本继榆;因此巾表,ZGC的重分配集只是決定了里面的存活對象會(huì)被重新復(fù)制到其他的Region中,里面的Region會(huì)被釋放略吨,而并不能說回收行為就只是針對這個(gè)集合里面的Region進(jìn)行集币,因?yàn)闃?biāo)記過程是針對全堆的
3、并發(fā)重分配:重分配是ZGC執(zhí)行過程中的核心階段
--這個(gè)過程要把重分配集中的存活對象復(fù)制到新的Region上翠忠,并為重分配集中的每個(gè)Region維護(hù)一個(gè)轉(zhuǎn)發(fā)表鞠苟,記錄從舊對象到新對象的轉(zhuǎn)向關(guān)系
--得益于染色指針的支持,ZGC收集器能僅從引用上就明確得知一個(gè)對象是否處于重分配集之中,如果用戶線程此時(shí)并發(fā)訪問了位于重分配集中的對象偶妖,這次訪問將會(huì)被預(yù)置的內(nèi)存屏障所截獲姜凄,然后立即根據(jù)Region上的轉(zhuǎn)發(fā)表記錄將訪問轉(zhuǎn)發(fā)到新復(fù)制的對象上,并同時(shí)修正更新該引用的值趾访,使其直接指向新對象态秧,ZGC將這種行為稱為指針的“自愈”能力
--這樣做的好處是只有第一次訪問舊對象會(huì)陷入轉(zhuǎn)發(fā),也就是只慢一次
--由于染色指針的存在扼鞋,一旦重分配集中某個(gè)Region的存活對象都復(fù)制完畢后申鱼,這個(gè)Region就可以立即釋放用于新對象的分配(但是轉(zhuǎn)發(fā)表還得留著不能釋放掉),哪怕堆中還有很多指向這個(gè)對象的未更新指針也沒有關(guān)系云头,這些舊指針一旦被使用捐友,它們都是可以自愈的
4、并發(fā)重映射:重映射所做的就是修正整個(gè)堆中指向重分配集中舊對象的所有引用
--并不是一個(gè)必須要“迫切”去完成的任務(wù)溃槐,因?yàn)榧词故桥f引用匣砖,它也是可以自愈的,最多只是第一次使用時(shí)多一次轉(zhuǎn)發(fā)和修正操作昏滴,重映射清理這些舊引用的主要目的是為了不變慢(還有清理結(jié)束后可以釋放轉(zhuǎn)發(fā)表這樣的附帶收益)猴鲫,所以說這并不是很“迫切”
--ZGC把并發(fā)重映射階段要做的工作,合并到了下一次垃圾收集循環(huán)中的并發(fā)標(biāo)記階段里去完成谣殊,反正它們都是要遍歷所有對象的拂共,這樣合并就節(jié)省了一次遍歷對象圖的開銷,一旦所有指針都被修正之后姻几,原來記錄新舊對象關(guān)系的轉(zhuǎn)發(fā)表就可以釋放掉了
優(yōu)缺點(diǎn):
1宜狐、ZGC完全沒有使用記憶集,它甚至連分代都沒有蛇捌,連像CMS中那樣只記錄新生代和老年代間引用的卡表也不需要抚恒,因而完全沒有用到寫屏障,所以給用戶線程帶來的運(yùn)行負(fù)擔(dān)也要小得多
2豁陆、ZGC的這種選擇也限制了它能承受的對象分配速率不會(huì)太高柑爸,ZGC準(zhǔn)備要對一個(gè)很大的堆做一次完整的并發(fā)收集,假設(shè)其全過程要持續(xù)十分鐘以上盒音,在這段時(shí)間里面
由于應(yīng)用的對象分配速率很高,將創(chuàng)造大量的新對象馅而,這些新對象很難進(jìn)入當(dāng)次收集的標(biāo)記范 圍祥诽,通常就只能全部當(dāng)作存活對象來看待——盡管其中絕大部分對象都是朝生夕滅的,這就產(chǎn)生了大量的浮動(dòng)垃圾瓮恭,如果這種高速分配持續(xù)維持的話雄坪,每一次完整的并發(fā)收集周期都會(huì)很長,回收到的內(nèi)存空間持續(xù)小于期間并發(fā)產(chǎn)生的浮動(dòng)垃圾所占的空間屯蹦,堆中剩余可騰挪的空間就越來越小了
3维哈、目前唯一的辦法就是盡可能地增加堆容量大小绳姨,獲得更多喘息的時(shí)間,但是若要從根本上提升ZGC能夠應(yīng)對 的對象分配速率阔挠,還是需要引入分代收集飘庄,讓新生對象都在一個(gè)專門的區(qū)域中創(chuàng)建,然后專門針對這個(gè)區(qū)域進(jìn)行更頻繁购撼、更快的收集
內(nèi)存分配與回收策略
對象的內(nèi)存分配跪削,應(yīng)該都是在堆上分配(而實(shí)際上也有可能經(jīng)過即時(shí)編譯后被拆散 為標(biāo)量類型并間接地在棧上分配)
在經(jīng)典分代的設(shè)計(jì)下,新生對象通常會(huì)分配在新生代中迂求,少數(shù)情況下(如對象大小超過一定閾值)也可能會(huì)直接分配在老年代
1碾盐、大對象直接進(jìn)入老年代:在Java虛擬機(jī)中要避免大對象的原因是,在分配空間時(shí)揩局,它容易導(dǎo)致內(nèi)存明明還有不少空間時(shí)就提前觸發(fā)垃圾收集毫玖,以獲取足夠的連續(xù)空間才能安置好它們,而當(dāng)復(fù)制對象時(shí)凌盯,大對象就意味著高額的內(nèi)存復(fù)制開銷
2孕豹、長期存活的對象將進(jìn)入老年代:對象通常在Eden區(qū)里誕生,如果經(jīng)過第一次 Minor GC后仍然存活十气,并且能被Survivor容納的話励背,該對象會(huì)被移動(dòng)到Survivor空間中,并且將其對象年齡設(shè)為1歲砸西,對象在Survivor區(qū)中每熬過一次Minor GC叶眉,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15)芹枷,就會(huì)被晉升到老年代中
3衅疙、空間分配擔(dān)保:
--在發(fā)生Minor GC之前,虛擬機(jī)必須先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間鸳慈,如果這個(gè)條件成立饱溢,那這一次Minor GC可以確保是安全的
--如果不成立,會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小走芋,如果大于绩郎,將嘗試進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險(xiǎn)的翁逞;如果小于肋杖,就要進(jìn)行一次Full GC
JVM-逃逸分析
逃逸分析的基本原理
1、分析對象動(dòng)態(tài)作用域挖函,當(dāng)一個(gè)對象在方法里面被定義后状植,它可能被外部方法所引用,如作為調(diào)用參數(shù)傳遞到其他方法中,這種稱為方法逃逸津畸;甚至還有可能被外部線程訪問到振定,如賦值給可以在其他線程中訪問的實(shí)例變量,這種稱為線程逃逸肉拓;從不逃逸后频、方法逃逸到線程逃逸,稱為對象由低到高的不同逃逸程度
2帝簇、如果能證明一個(gè)對象不會(huì)逃逸到方法或線程之外(即別的方法或線程無法通過任何途徑訪問到這個(gè)對象)徘郭,或者逃逸程度比較低(只逃逸出方法而不會(huì)逃逸出線程),則可能為這個(gè)對象實(shí)例采取不同程度的優(yōu)化
棧上分配
1丧肴、在Java虛擬機(jī)中残揉,Java堆中的對象對于各個(gè)線程都是共享和可見的,只要持有這個(gè)對象的引用芋浮,就可以訪問到堆中存儲(chǔ)的對象數(shù)據(jù)
2抱环、虛擬機(jī)的垃圾收集子系統(tǒng)會(huì)回收堆中不再使用的對象,但回收動(dòng)作無論是標(biāo)記篩選出可回收對象纸巷,還是回收和整理內(nèi)存镇草,都需要耗費(fèi)大量資源
3、如果確定一個(gè)對象不會(huì)逃逸出線程之外瘤旨,就可以將這個(gè)對象在棧上分配內(nèi)存梯啤,對象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀
4、實(shí)際上完全不會(huì)逃逸的局部對象和不會(huì)逃逸出線程的對象所占的比例是很大的存哲,如果能使用棧上分配因宇,那大量的對象就會(huì)隨著方法的結(jié)束而自動(dòng)銷毀了,垃圾收集子系統(tǒng)的壓力將會(huì)下降很多
5祟偷、棧上分配可以支持方法逃逸察滑,但不能支持線程逃逸
標(biāo)量替換
1、若一個(gè)數(shù)據(jù)已經(jīng)無法再分解成更小的數(shù)據(jù)來表示了修肠,Java虛擬機(jī)中的原始數(shù)據(jù)類型(int贺辰、long等數(shù)值類型及reference類型等)都不能再進(jìn)一步分解了,那么這些數(shù)據(jù) 就可以被稱為標(biāo)量嵌施,相對的饲化,如果一個(gè)數(shù)據(jù)可以繼續(xù)分解,那它就被稱為聚合量艰管,Java 中的對象就是典型的聚合量
2滓侍、如果把一個(gè)Java對象拆散,根據(jù)程序訪問的情況牲芋,將其用到的成員變量恢復(fù)為原始類型來訪問,這個(gè)過程就稱為標(biāo)量替換
3、如果逃逸分析能夠證明一個(gè)對象不會(huì)被方法外部訪問缸浦,并且這個(gè)對象可以被拆散夕冲,那么程序真正執(zhí)行的時(shí)候?qū)⒖赡懿蝗?chuàng)建這個(gè)對象,而改為直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用的成員變量來代替
4裂逐、將對象拆分后歹鱼,除了可以讓對象的成員變量在棧上 (棧上存儲(chǔ)的數(shù)據(jù),很大機(jī)會(huì)被虛擬機(jī)分配至物理機(jī)器的高速寄存器中存儲(chǔ))分配和讀寫之外卜高,還可以為后續(xù)進(jìn)一步的優(yōu)化手段創(chuàng)建條件
5弥姻、標(biāo)量替換可以視作棧上分配的一種特例,實(shí)現(xiàn)更簡單(不用考慮整個(gè)對象完整結(jié)構(gòu)的分配)掺涛,但對逃逸程度的要求更高庭敦,它不允許對象逃逸出方法范圍內(nèi)
同步消除
線程同步本身是一個(gè)相對耗時(shí)的過程,如果逃逸分析能夠確定一個(gè)變量不會(huì)逃逸出線程薪缆,無法被其他線程訪問秧廉,那么這個(gè)變量的讀寫肯定就不會(huì)有競爭, 對這個(gè)變量實(shí)施的同步措施也就可以安全地消除掉
后端編譯優(yōu)化步驟
1拣帽、方法內(nèi)聯(lián)優(yōu)化
2疼电、逃逸分析
3、無效代碼刪除
線程-java內(nèi)存模型(共享內(nèi)存模型)
定義
1减拭、在并發(fā)編程中蔽豺,需要處理兩個(gè)關(guān)鍵問題:線程之間如何通信及同步;而Java線程之間的通信由Java內(nèi)存模型控制拧粪,Java內(nèi)存模型決定一個(gè)線程對共享變量的寫入何時(shí)對另一個(gè)線程可見(但是會(huì)存在內(nèi)存可見性問題修陡,需要通過顯式的同步機(jī)制去處理線程間的執(zhí)行順序(相對)問題)
2、Java內(nèi)存模型將所有的共享變量都存儲(chǔ)在主內(nèi)存中(虛擬機(jī)內(nèi)存的一部分)既们,每個(gè)線程還有自己的工作內(nèi)存
3濒析、線程的工作內(nèi)存中保存了被該線程使用的共享變量的主內(nèi)存副本( 不是整個(gè)對象,而是對象中具體的變量)啥纸,線程對共享變量的所有讀寫操作都必須在工作內(nèi)存中進(jìn)行号杏,而不能直接讀寫主內(nèi)存中的數(shù)據(jù)
4、不同的線程之間無法直接訪問對方工作內(nèi)存中的變量斯棒,線程間變量值的傳遞均需要通過主內(nèi)存來完成(線程A把本地內(nèi)存中更新過的共享變量刷新到主內(nèi)存中去盾致,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量)
順序一致性內(nèi)存模型(理論模型)
特性:
1、一個(gè)線程中的所有操作必須按照程序的順序來執(zhí)行
2荣暮、無論程序是否同步庭惜,所有線程都只能看到一個(gè)單一的操作執(zhí)行順序;在順序一致性內(nèi)存模型中穗酥,每個(gè)操作都必須原子執(zhí)行且立刻對所有線程可見
JMM特性:
1护赊、未同步程序在JMM中不但整體的執(zhí)行順序是無序的惠遏,而且所有線程看到的操作執(zhí)行順序也可能不一致
2、當(dāng)前線程把數(shù)據(jù)緩存在本地內(nèi)存中骏啰,在沒有刷新到主內(nèi)存之前节吮,這個(gè)寫操作僅對當(dāng)前線程可見;只有當(dāng)前線程把本地內(nèi)存中寫過的數(shù)據(jù)刷新到主內(nèi)存之后判耕,這個(gè)寫操作才能對其他線程可見透绩;因此,當(dāng)前線程和其他線程看到的操作執(zhí)行順序?qū)⒉灰恢?br>
3壁熄、臨界區(qū)內(nèi)的代碼可以重排序(但JMM不允許臨界區(qū)內(nèi)的代碼“逸出”到臨界區(qū)之外帚豪,那樣會(huì)破壞監(jiān)視器的語義),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í)行順序
原子性、可見性仙蚜、有序性
Java內(nèi)存模型需要處理3個(gè)問題:原子性此洲、可見性和有序性
原子性:
1、由JMM來直接保證的原子性變量操作包括read委粉、load呜师、assign、use贾节、store和write這六個(gè)汁汗, 基本數(shù)據(jù)類型的訪問、讀寫都是具備原子性的(例外就是long和double的非原子性協(xié)定)
2栗涂、如果應(yīng)用場景需要一個(gè)更大范圍的原子性保證(經(jīng)常會(huì)遇到)知牌,JMM還提供了lock和unlock操作來滿足這種需求,盡管虛擬機(jī)未把lock和unlock操作直接開放給用戶使用斤程,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式地使用這兩個(gè)操作角寸;這兩個(gè)字節(jié)碼指令反映到Java 代碼中就是同步塊——synchronized關(guān)鍵字,因此在synchronized塊之間的操作也具備原子性
可見性:
1忿墅、可見性就是指當(dāng)一個(gè)線程修改了共享變量的值時(shí)扁藕,其他線程能夠立即得知這個(gè)修改---JMM是通過在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存通信的方式來實(shí)現(xiàn)可見性的疚脐,無論是普通變量還是volatile變量都是如此
2亿柑、普通變量與volatile變量的區(qū)別是,volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存棍弄,以及每次使用前立即從主內(nèi)存刷新望薄;因此我們可以說volatile保證了多線程操作 時(shí)變量的可見性疟游,而普通變量則不能保證這一點(diǎn)
3、同步塊的可見性是由“對一個(gè)變量執(zhí)行unlock操作之前式矫,必須先把此變量同步回主內(nèi)存中(執(zhí)行store乡摹、write操作)”這條規(guī)則獲得的
4役耕、final關(guān)鍵字的可見性是指:被final修飾的字段在構(gòu)造器中一旦被初始化完成采转,并且構(gòu)造器沒有把“this”的引用傳遞出去(this引用逃逸是一件很危險(xiǎn)的事情,其他線程有可能通過這個(gè)引用訪問到“初始化了一半”的對象)瞬痘,那么在其他線程中就能看見final字段的值
有序性:
1故慈、線程內(nèi)所有的操作都是有序的(串行語義);線程間所有的操作是無序的(指令重排序框全、工作內(nèi)存與主內(nèi)存同步延遲)
2察绷、Java語言提供了volatile和synchronized兩個(gè)關(guān)鍵字來保證線程之間操作的有序性,---volatile關(guān)鍵字本身就包含了禁止指令重排序的語義
3津辩、synchronized則是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對其進(jìn)行l(wèi)ock操作”這條規(guī)則獲得的拆撼,這個(gè)規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行地進(jìn)入
先行發(fā)生原則-happens-before
定義:在JMM中,如果一個(gè)操作執(zhí)行的結(jié)果需要對另一個(gè)操作可見喘沿,那么這兩個(gè)操作之間必須要存在先行發(fā)生關(guān)系闸度,先行發(fā)生原則用于描述內(nèi)存可見性;兩個(gè)操作既可以是在一個(gè)線程之內(nèi)蚜印,也可以是在不同線程之間
兩個(gè)操作之間具有先行發(fā)生關(guān)系莺禁,并不意味著前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行,先行發(fā)生僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對后一個(gè)操作可見窄赋,且前一個(gè)操作按順序排在第二個(gè)操作之前
JMM先行發(fā)生(不是時(shí)間上的先后)規(guī)則:
1哟冬、程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,先行發(fā)生于該線程中的任意后續(xù)操作
2忆绰、監(jiān)視器鎖規(guī)則:對一個(gè)鎖的解鎖浩峡,先行發(fā)生(時(shí)間上的先后)于隨后對這個(gè)鎖的加鎖
3、volatile變量規(guī)則:對一個(gè)volatile變量的寫错敢,先行發(fā)生(時(shí)間上的先后)于任意后續(xù)對這個(gè)volatile變量的 讀
4翰灾、Thread對象的start方法先行發(fā)生于此線程的每一個(gè)動(dòng)作
5、線程中的所有操作都先行發(fā)生于對此線程的終止檢測
6伐债、對線程interrupt方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生
7预侯、一個(gè)對象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的 finalize方法的開始
8、傳遞性:如果A先行發(fā)生于B峰锁,且B先行發(fā)生于C萎馅,那么A先行發(fā)生于C
volatile(可見性、有序性)
volatile讀:如果主內(nèi)存變量值有更新虹蒋,將本地內(nèi)存中變量值失效糜芳,直接從主內(nèi)存中讀取最新值
volatile寫:更新本地內(nèi)存的同時(shí)飒货,會(huì)立即刷新到主內(nèi)存(主內(nèi)存更新時(shí)會(huì)通知其他線程)
synchronized(可見性、有序性峭竣、原子性塘辅,互斥可重入,鎖是存放在對象頭里面的)
同步塊:monitorenter和monitorexit指令實(shí)現(xiàn)
同步方法:依靠方法修飾符上的ACC_SYNCHRONIZEN同步位來實(shí)現(xiàn)
如果線程獲取鎖失敗皆撩,線程進(jìn)入同步隊(duì)列扣墩,線程狀態(tài)變?yōu)锽LOCKED
當(dāng)獲取鎖的線程釋放了鎖,會(huì)同時(shí)喚醒阻塞在同步隊(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ì)返回)
鎖升級(不能降級):
1濒持、無鎖
2、偏向鎖:Mark Word中存偏向線程id寺滚、對象分代年齡柑营、是否是偏向鎖標(biāo)志、鎖標(biāo)志位玛迄;單線程時(shí)由境,避免了CAS加鎖/解鎖,只需判斷偏向線程id是否是自己蓖议,如果不是虏杰,且現(xiàn)在是偏向鎖,則CAS將偏向線程id替換為自己勒虾,否則纺阔,CAS競爭鎖,到全局安全點(diǎn)修然,暫停偏向線程笛钝,釋放鎖,鎖變成無鎖狀態(tài)(偏向線程已經(jīng)執(zhí)行完邏輯)或輕量級鎖
3愕宋、輕量級鎖:Mark Word中存放指向持有鎖線程的棧中鎖記錄的指針玻靡、鎖標(biāo)志位;CAS替換鎖記錄指針中贝,失敗囤捻,則自旋(避免上下文切換)+CAS獲取鎖,超過一定時(shí)間(次數(shù))膨脹為重量級鎖邻寿;追求響應(yīng)時(shí)間
4蝎土、重量級鎖:Mark Word中存指向獲取鎖的線程的棧中鎖記錄的指針视哑、鎖標(biāo)志位;線程阻塞誊涯,不使用自旋挡毅,不消耗CPU;追求吞吐量
final(可見性暴构、有序性跪呈、原子性)
final寫:在構(gòu)造方法內(nèi)的final寫與被構(gòu)造對象的引用賦值,禁止重排序
final讀:初次讀對象引用與隨后讀對象的final常量丹壕,禁止重排序
JUC-AQS
1庆械、AQS支持獨(dú)占鎖(如ReentrantLock、ReadWriteLock的寫鎖)和共享鎖(如CountDownLatch菌赖、ReadWriteLock的讀鎖)
2、state:表示資源數(shù)沐序,volatile修飾琉用,需要保證可見性、有序性策幼、利用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來喚醒后繼節(jié)點(diǎn))浅浮、條件狀態(tài)(當(dāng)前節(jié)點(diǎn)為條件隊(duì)列節(jié)點(diǎn),這個(gè)狀態(tài)在同步隊(duì)列里不會(huì)被用到)捷枯、傳播狀態(tài)(針對共享鎖滚秩,設(shè)置在head節(jié)點(diǎn)releaseShared(釋放共享鎖)操作需要被傳遞到下一個(gè)節(jié)點(diǎn),用來保證后繼節(jié)點(diǎn)可以獲取共享資源)
4淮捆、nextWaiter屬性:連接下一個(gè)等待condition的節(jié)點(diǎn)郁油,或者在共享模式下作為一個(gè)特殊節(jié)點(diǎn)保存,用來判斷是否為共享模式
5攀痊、同步隊(duì)列:雙向鏈表桐腌,隊(duì)尾插入時(shí)會(huì)有線程競爭(自旋+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)前線程中間被中斷過翔始,也可以自旋+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)前線程中間被中斷過肮砾,也可以自旋+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)前線程沒有加入到同步隊(duì)列時(shí)置蜀,進(jìn)行自旋,并阻塞當(dāng)前線程悉盆,直到當(dāng)前線程被喚醒(從條件隊(duì)列移到同步隊(duì)列)或期間被中斷盯荤,并記錄中斷狀態(tài);在同步隊(duì)列中自旋+CAS嘗試獲取資源焕盟;如果當(dāng)前節(jié)點(diǎn)的nextWaiter不為空秋秤,說明節(jié)點(diǎn)在獲取鎖時(shí)由于異常或者被中斷而被取消京髓,此時(shí)需要移除等待隊(duì)列中取消狀態(tài)的節(jié)點(diǎn)航缀;如果期間被中斷過,拋出中斷異常
**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鎖的過程寫在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ū)別):如果沒有線程獲取資源苛聘,CAS嘗試獲取資源,設(shè)置當(dāng)前線程持有資源忠聚,如果當(dāng)前線程已經(jīng)獲取過資源(可重入)设哗,在原來基礎(chǔ)上+1(不需要CAS,因?yàn)闊o競爭)两蟀,成功直接返回网梢;失敗則阻塞線程,創(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)前線程中間被中斷過膛堤,也可以自旋+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ū)別):如果沒有線程獲取資源次企,CAS嘗試獲取資源怯晕,設(shè)置當(dāng)前線程持有資源,如果當(dāng)前線程已經(jīng)獲取過資源(可重入)缸棵,在原來基礎(chǔ)上+1(不需要CAS舟茶,因?yàn)闊o競爭),成功直接返回;失敗則阻塞線程吧凉,創(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)前線程中間被中斷過凄诞,也可以自旋+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競爭;喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)(中間會(huì)移除取消狀態(tài)的節(jié)點(diǎn))戈稿,后繼節(jié)點(diǎn)自旋+CAS嘗試獲取資源西土,獲取成功,則從acquire方法返回
3鞍盗、tryLock方法:調(diào)用Sync的nonfairTryAcquire方法嘗試請求獲取資源(不會(huì)判斷當(dāng)前節(jié)點(diǎn)前是否還有節(jié)點(diǎn)翠储,非公平):如果沒有線程獲取資源,CAS嘗試獲取資源橡疼,設(shè)置當(dāng)前線程持有資源,如果當(dāng)前線程已經(jīng)獲取過資源(可重入)庐舟,在原來基礎(chǔ)上+1(不需要CAS欣除,因?yàn)闊o競爭),成功直接返回挪略;失敗則阻塞線程历帚,創(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)前線程中間被中斷過,也可以自旋+CAS嘗試獲取資源);如果自旋+CAS嘗試獲取資源出現(xiàn)異常睹栖,要將當(dāng)前節(jié)點(diǎn)狀態(tài)置為取消
4硫惕、等待通知機(jī)制:使用AQS的Condition實(shí)現(xiàn)
JUC-CountDownLatch
CountDownLatch是一個(gè)同步輔助類,是通過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)前線程中間被中斷過渗稍,也可以自旋+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ù)了一對相關(guān)的鎖:共享鎖readLock和獨(dú)占鎖writeLock
共享鎖readLock用于讀操作尝苇,能同時(shí)被多個(gè)線程獲阮踔弧埠胖;獨(dú)占鎖writeLock用于寫入操作,只能被一個(gè)線程持有(支持鎖降級:持有寫鎖的線程可以在寫鎖未釋放之前獲得讀鎖)
Condition只有在寫鎖中用到格仲,讀鎖是不支持Condition的
內(nèi)部有一個(gè)Sync類繼承自AQS押袍,有兩個(gè)子類FairSync和NonfairSync:讀鎖和寫鎖共用一個(gè)狀態(tài),高16位標(biāo)識讀計(jì)數(shù)了凯肋,低16位標(biāo)識寫重入次數(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ù)獲取讀鎖,沒有線程獲取寫鎖悄雅,可以嘗試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)前線程中間被中斷過纽什,也可以自旋+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)建滔驶,而是通過 ThreadPoolExecutor 的方式(規(guī)避資源耗盡的風(fēng)險(xiǎn))
原因:
Executors可以創(chuàng)建3種類型的ThreadPoolExecutor:SingleThreadExecutor遇革、FixedThreadPool和CachedThreadPool
1、FixedThreadPool 和 SingleThreadPool:阻塞隊(duì)列長度為 Integer.MAX_VALUE揭糕,可能會(huì)堆積大量的請求萝快,從而導(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:用來暫時(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叁怪、線程池工廠:可以通過工廠為線程設(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í)間最長的任務(wù)生兆,并執(zhí)行當(dāng)前提交的任務(wù)难捌,如果線程池已經(jīng)關(guān)閉,任務(wù)將被丟棄
5鸦难、也可以自定義拒絕策略
原理:(為什么要這樣設(shè)計(jì):避免獲取全局鎖根吁,完成預(yù)熱之后,幾乎都是在執(zhí)行第的二步)
1合蔽、向線程池提交任務(wù):execute方法(沒有返回值)和submit方法(有返回值future击敌,通過future的get方法可以獲取返回值(阻塞當(dāng)前線程,直到任務(wù)完成拴事,返回結(jié)果))
2沃斤、如果當(dāng)前運(yùn)行的線程數(shù)少于corePoolSize圣蝎,則創(chuàng)建新線程來執(zhí)行任務(wù),否則向下執(zhí)行(需要獲取全局鎖)
3衡瓶、如果工作隊(duì)列未滿徘公,將任務(wù)加入工作隊(duì)列,否則向下執(zhí)行
4哮针、如果當(dāng)前運(yùn)行的線程數(shù)少于maximumPoolSize关面,則創(chuàng)建新線程來執(zhí)行任務(wù),否則向下執(zhí)行(需要獲取全局鎖)
5诚撵、執(zhí)行拒絕策略
6缭裆、關(guān)閉線程池:shutdown(推薦)和shutdownNow方法,遍歷線程池中的工作線程寿烟,逐個(gè)調(diào)用線程的interrupt方法來中斷線程(無法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無法終止)澈驼;shutdown中斷的是所有沒有正在執(zhí)行任務(wù)的線程(還沒停止),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)用場景漠其,即負(fù)載比較重的服務(wù)器
1、FixedThreadPool的corePoolSize和maximumPoolSize都被設(shè)置為創(chuàng)建FixedThreadPool時(shí)指定的參數(shù)nThreads
2竿音、當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí)和屎,keepAliveTime為多余的空閑線程等待新任務(wù)的最長時(shí)間,超過這個(gè)時(shí)間后多余的線程將被終止春瞬;這里把keepAliveTime設(shè)置為0L柴信,意味著多余的空閑線程會(huì)被立即終止
3、FixedThreadPool使用無界隊(duì)列LinkedBlockingQueue作為線程池的工作隊(duì)列(隊(duì)列的容量為Integer.MAX_VALUE)(不會(huì)拒絕任務(wù)宽气,可能會(huì)堆積大量的請求随常,從而導(dǎo)致 OOM)
4、當(dāng)線程池中的線程數(shù)達(dá)到corePoolSize后萄涯,新任務(wù)將在無界隊(duì)列中等待绪氛,因此線程池中的線程數(shù)不會(huì)超過corePoolSize
5、使用無界隊(duì)列時(shí)maximumPoolSize和keepAliveTime將是效參數(shù)
SingleThreadExecutor:單個(gè)線程(如果內(nèi)部工作線程由于異常而被終止涝影,則會(huì)新建一個(gè)線程替代)的線程池钞楼,適用于需要保證順序地執(zhí)行各個(gè)任務(wù),并且在任意時(shí)間點(diǎn)袄琳,不會(huì)有多個(gè)線程是活動(dòng)的應(yīng)用場景
1询件、SingleThreadExecutor的corePoolSize和maximumPoolSize被設(shè)置為1
2、其他參數(shù)與FixedThreadPool相同
3唆樊、SingleThreadExecutor使用無界隊(duì)列LinkedBlockingQueue作為線程池的工作隊(duì)列(隊(duì)列的容量為Integer.MAX_VALUE)
CachedThreadPool:大小無界的線程池宛琅,適用于執(zhí)行很多的短期異步任務(wù)的小程序,或者是負(fù)載較輕的服務(wù)器
1逗旁、CachedThreadPool的corePoolSize被設(shè)置為0嘿辟,即corePool為空;maximumPoolSize被設(shè)置為Integer.MAX_VALUE片效,即maximumPool是無界的
2红伦、keepAliveTime設(shè)置為60L,意味著CachedThreadPool中的空閑線程等待新任務(wù)的最長時(shí)間為60秒淀衣,空閑線程超過60秒后將會(huì)被終止
3昙读、CachedThreadPool使用沒有容量的SynchronousQueue作為線程池的工作隊(duì)列,但CachedThreadPool的maximumPool是無界的膨桥;這意味著蛮浑,如果主線程提交任務(wù)的速度高于maximumPool中線程處理任務(wù)的速度時(shí),CachedThreadPool會(huì)不斷創(chuàng)建新線程只嚣;極端情況下沮稚, CachedThreadPool會(huì)因?yàn)閯?chuàng)建過多線程而耗盡CPU和內(nèi)存資源
5、SynchronousQueue是一個(gè)沒有容量的阻塞隊(duì)列册舞,每個(gè)插入操作必須等待另一個(gè)線程的對應(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操作配對成功,主線程把任務(wù)交給空閑線程執(zhí)行贯钩,execute()方法執(zhí)行完成募狂;否則向下執(zhí)行
2、當(dāng)初始maximumPool為空角雷,或者maximumPool中當(dāng)前沒有空閑線程時(shí)祸穷,將沒有線程執(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ì)被終止缎除,因此長時(shí)間保持空閑的CachedThreadPool不會(huì)使用任何資源