3 分配
3.1 鎖堆分配
當(dāng)所需要的空間大于512字節(jié)或者在已有的緩存中無法分配所需的大小,就會(huì)發(fā)生鎖堆分配,顧名思義,鎖堆分配需要獲取堆上的鎖,所以應(yīng)該盡量避免。
If size < 512 or enough space in cache try cacheAlloc return if OK HEAP_LOCK Do If there is a big enough chunk on freelist takeit goto Got it else manageAllocFailure if any error goto Get out End Do Cot it: Initialise object Get out: HEAP_UNLOCK |
圖表 7 鎖堆分配
圖表7即是鎖堆分配的偽代碼,垃圾收集器首先檢測(cè)要分配的空間的大小,如果小于512字節(jié)或者在當(dāng)前緩存內(nèi)有足夠的空間進(jìn)行分配,則嘗試緩存分配;如果不能進(jìn)行緩存分配,或者緩存分配失敗,則會(huì)發(fā)生鎖堆。垃圾收集器開始在空閑鏈表中查詢可用的空間,如果找到滿足條件的空間,則在此空間內(nèi)進(jìn)行分配,并且將剩余的空間返回到空閑鏈表中。需要注意的是,如果剩余的空閑空間不足512字節(jié)加上頭信息大小(32位架構(gòu)上12字節(jié),64架構(gòu)上24字節(jié)),那么就不會(huì)返回到空閑鏈表中,這些小的存儲(chǔ)空間就被稱為“暗物質(zhì)”。如果垃圾收集器無法找到滿足條件的空閑空間,就會(huì)發(fā)生分配失敗,開始進(jìn)行垃圾收集工作。垃圾收集結(jié)束之后,繼續(xù)在空閑鏈表中查找滿足需求的空間,如果依然無法找到,就會(huì)發(fā)生內(nèi)存溢出的錯(cuò)誤。無論對(duì)象是否分配成功,堆鎖都會(huì)被釋放。
3.1.1 小提示
在某些情況下,比如在很大的堆中,空閑鏈表是由大量的空閑存儲(chǔ)空間段組成,或者應(yīng)用頻繁的申請(qǐng)較大的存儲(chǔ)空間,鎖堆分配策略可能會(huì)存在問題。因?yàn)槊看味夹枰獜目臻e鏈表的頭開始查找滿足需求的空閑空間,效率相對(duì)較低,所以有了快速空閑鏈表查找算法來解決這個(gè)問題。
每次鎖堆分配嘗試查找鏈表時(shí),都會(huì)收集以下數(shù)據(jù):
· 在找到滿足需求的空間之前已經(jīng)在空閑鏈表中查找過的存儲(chǔ)段數(shù)量
· 在找到滿足需求的空間之前所查找的空閑段中的最大段的大小。也就是不能滿足需求的段的最大大小
當(dāng)找到滿足需求的空間之后,如果查找計(jì)數(shù)大于20,表示需要?jiǎng)?chuàng)建一個(gè)active hint指向空閑鏈表。然后,根據(jù)實(shí)際的需求來決定是從空閑鏈表頭開始查找,還是從active hint指向的位置開始查找,一旦在一個(gè)段中進(jìn)行了分配,active hint會(huì)被及時(shí)更新。
3.2 緩存分配
圖表 8 堆中的緩存段
緩存分配是針對(duì)小對(duì)象設(shè)計(jì)的高性能分配策略,線程預(yù)先從堆中分配一段空間,對(duì)象直接在這一段空間內(nèi)進(jìn)行分配,無需獲取堆的鎖,所以緩存分配的效率是極高的。如果滿足以下條件,就會(huì)進(jìn)行緩存分配:
· 對(duì)象小于512字節(jié),或者
· 線程本地堆中有足夠的空間容納對(duì)象
圖表8是堆中緩存段的示意圖。緩存段又叫做線程本地堆(thread local heap,TLH)。當(dāng)垃圾收集器為一個(gè)線程分配線程本地堆時(shí),使用鎖堆分配一段空間給該線程獨(dú)享使用。分配集合中對(duì)應(yīng)線程本地堆的比特位不會(huì)被設(shè)置,直到線程本地堆滿了,或者一輪垃圾收集工作開始的時(shí)候,才會(huì)被設(shè)置。為了提高分配線程本地堆的性能,垃圾收集器總是使用在空閑鏈表中找到的第一個(gè)空閑段,并且不大于40KB。
圖表 9 在緩存段中分配的對(duì)象
圖表9顯示的是在緩存段中分配的一些對(duì)象。在緩存段中,對(duì)象總是從段頂開始分配,這樣能夠比從段底分配更加高效。圖表9中還顯示了在分配集合中,并沒有設(shè)置緩存段的標(biāo)識(shí)位。直到緩存段滿了,或者一輪垃圾收集工作開始的時(shí)候,這些標(biāo)識(shí)位才會(huì)被設(shè)置。
4 垃圾收集
在鎖堆分配中如果發(fā)生了分配失敗,或者有對(duì)System.gc()的顯式調(diào)用,則會(huì)發(fā)生垃圾收集。調(diào)用System.gc()的線程或者發(fā)生分配失敗的線程負(fù)責(zé)進(jìn)行垃圾收集。首先,他獲取垃圾收集所需的鎖,然后掛起其他線程,再開始垃圾收集的三個(gè)階段的工作:標(biāo)識(shí)、清理以及壓縮階段(非必須的)。IBM JDK的垃圾收集是stop-the-world類型的,因?yàn)樵诶占^程中,所有的應(yīng)用線程都被掛起。
4.1 標(biāo)識(shí)階段
在標(biāo)識(shí)階段,所有的活動(dòng)對(duì)象都被標(biāo)識(shí)。因?yàn)椴豢傻竭_(dá)對(duì)象不太容易定位,所以要明確所有的可到達(dá)對(duì)象,那么余下的就是垃圾了。這個(gè)標(biāo)識(shí)所有可達(dá)到對(duì)象的過程也被稱作追蹤(tracing)。
被保存的寄存器、線程的執(zhí)行棧、類中的靜態(tài)域、本地或者全局的JNI引用,共同構(gòu)成了虛擬機(jī)的活動(dòng)狀態(tài)。虛擬機(jī)自身調(diào)用的函數(shù)都會(huì)生成一個(gè)C執(zhí)行棧上的一個(gè)幀。這個(gè)幀可能包含一些對(duì)象實(shí)例,可能是要賦值給本地變量的對(duì)象,也可能是來自調(diào)用者的調(diào)用參數(shù)。在追蹤階段,所有這些引用都是被同等對(duì)待的。垃圾收集器自頂?shù)降讙呙杳總€(gè)線程的棧,4字節(jié)為一組(在64位架構(gòu)上是8字節(jié)一組),垃圾收集器假設(shè)棧是4字節(jié)對(duì)齊的(在64位架構(gòu)上是8字節(jié)對(duì)齊的),然后檢查棧上每個(gè)4字節(jié)組是否是指向堆上的一個(gè)對(duì)象,有可能所指向的不是一個(gè)真正的對(duì)象,因?yàn)榭赡苤皇桥銮珊鸵粋€(gè)整數(shù)或者浮點(diǎn)數(shù)的存儲(chǔ)表示相同。垃圾收集器掃描線程棧,然后保守的處理他所找到的這些指針,只要這個(gè)指針指向一個(gè)對(duì)象地址,那么就假設(shè)他真的是一個(gè)對(duì)象引用,并且在垃圾收集的時(shí)候,不能移動(dòng)這個(gè)對(duì)象。如果滿足以下3個(gè)條件,這個(gè)槽位就被認(rèn)為是指向一個(gè)對(duì)象的指針:
1. 8字節(jié)對(duì)齊的
2. 位于堆的地址范圍內(nèi)
3. 對(duì)應(yīng)的分配比特位已經(jīng)設(shè)置為1
以這種方式被引用的對(duì)象就是根對(duì)象,根對(duì)象的dosed標(biāo)識(shí)位被設(shè)置,表示這個(gè)對(duì)象不能被移動(dòng),只有在壓縮階段時(shí),垃圾收集器才會(huì)設(shè)置dosed標(biāo)識(shí)位。從根對(duì)象開始,可以精確的追蹤其他被引用對(duì)象,因?yàn)槔占髦肋@些引用確實(shí)是真正指向?qū)ο蟮囊茫捎诳梢孕薷囊茫赃@些被引用的對(duì)象在壓縮階段可以被移動(dòng)。追蹤階段使用一個(gè)可以容納4KB條目的棧,所有的引用都被壓棧,同時(shí),設(shè)置對(duì)應(yīng)的標(biāo)識(shí)比特位。首先,全部根對(duì)象被壓棧,然后再依次出棧,在出棧的過程中繼續(xù)追蹤。普通對(duì)象(非數(shù)組對(duì)象)通過mptr訪問類信息塊來追蹤被其引用的其他對(duì)象。一旦找到引用,并且被引用對(duì)象尚未被標(biāo)識(shí),那么該對(duì)象就被標(biāo)識(shí)并且壓棧。
對(duì)于數(shù)組對(duì)象,垃圾收集器檢查每個(gè)條目,如果該對(duì)象尚未被標(biāo)識(shí),則對(duì)其進(jìn)行標(biāo)識(shí)并且壓棧。為了避免標(biāo)識(shí)棧溢出,每次只處理數(shù)組的一部分內(nèi)容。
垃圾收集器重復(fù)以上過程,直到標(biāo)識(shí)棧為空。
4.1.1 標(biāo)識(shí)棧溢出
因?yàn)闃?biāo)識(shí)棧的大小是有限制的,所有有可能發(fā)生標(biāo)識(shí)棧溢出的問題。雖然這種問題發(fā)生的幾率非常小,但是當(dāng)發(fā)生標(biāo)識(shí)棧溢出時(shí),對(duì)于垃圾收集的暫停時(shí)間有非常大的影響。
4.1.1.1 溢出集合
垃圾收集器需要一個(gè)能夠映射整個(gè)堆的比特位數(shù)組來記錄堆中未被追蹤的對(duì)象,就是FR_bits數(shù)組,該數(shù)組是為進(jìn)行增量壓縮(Incremental Compaction, IC)設(shè)置的,對(duì)于每個(gè)可能的引用槽位(在32位架構(gòu)上是4字節(jié),在64位架構(gòu)上是8字節(jié)),有一個(gè)對(duì)應(yīng)的比特位。由于JVMObject頭信息不會(huì)包含任何引用信息,所以每個(gè)對(duì)象對(duì)應(yīng)的FR_bits數(shù)組中的前2個(gè)比特位是不被IC使用的,因此垃圾收集器使用FR_bits數(shù)組中的第1個(gè)冗余比特位實(shí)現(xiàn)溢出集合。
4.1.1.2 處理非系統(tǒng)堆對(duì)象的標(biāo)識(shí)棧溢出
當(dāng)線程嘗試將一個(gè)引用壓棧到標(biāo)識(shí)棧時(shí),如果此時(shí)發(fā)現(xiàn)標(biāo)識(shí)棧已經(jīng)滿了,他會(huì)向自己的本地標(biāo)識(shí)隊(duì)列發(fā)布一個(gè)任務(wù)。如果發(fā)布動(dòng)作失敗了,線程會(huì)設(shè)置這個(gè)引用對(duì)象對(duì)應(yīng)的FR_bits數(shù)組,以表示發(fā)生了標(biāo)識(shí)棧溢出。
然后追蹤工作繼續(xù)處理已經(jīng)設(shè)置了FR_bits數(shù)組的、無法被壓棧的引用。
一旦線程處理完了標(biāo)識(shí)棧,他就會(huì)嘗試接管溢出集合,并且,為了確保溢出集合只被一個(gè)線程處理,該線程設(shè)置一個(gè)是否發(fā)生標(biāo)識(shí)棧溢出的全局標(biāo)識(shí)為False。一旦確立了溢出集合的所屬權(quán),這個(gè)線程就開始掃描FR_bits數(shù)組,查找所有的非零比特位。一旦找到非零的比特位,則將其清零,并且對(duì)應(yīng)的引用被壓棧。到一定量的引用被壓棧之后,他們被發(fā)布到本地標(biāo)識(shí)隊(duì)列,以便于其他線程輔助處理溢出集合。
在處理溢出集合的同時(shí),有可能發(fā)生標(biāo)識(shí)棧溢出。如果發(fā)生這種情況,那么一個(gè)全局標(biāo)識(shí)會(huì)被設(shè)置以標(biāo)識(shí)發(fā)生了溢出,上面描述的處理過程會(huì)重復(fù)執(zhí)行。
4.1.1.3 系統(tǒng)堆溢出機(jī)制
在收集根對(duì)象時(shí),垃圾收集器會(huì)將所有系統(tǒng)堆和ACS堆中的對(duì)象引用也壓棧,因此同樣會(huì)發(fā)生標(biāo)識(shí)棧溢出的問題。但是FR_bits數(shù)組只映射了非系統(tǒng)堆,所以無法用來記錄系統(tǒng)堆和ACS堆中未被追蹤的對(duì)象。
在垃圾收集的標(biāo)識(shí)階段,已經(jīng)加載的類的地址是不會(huì)被修改的,因此,垃圾收集器需要記錄在發(fā)生標(biāo)識(shí)棧溢出前的那一刻,他所到達(dá)的追蹤鏈條的位置。所以,引入兩個(gè)全局變量“overflowSystemClasses”和“overflowACSClasses”來表示對(duì)應(yīng)在系統(tǒng)堆和ACS堆中的進(jìn)行位置。當(dāng)處理溢出集合時(shí),這兩個(gè)變量告訴垃圾收集器應(yīng)該在什么位置停止。
4.1.1.4 處理系統(tǒng)堆對(duì)象的標(biāo)識(shí)棧溢出
在并行標(biāo)識(shí)(parallelMark)階段,如果線程已經(jīng)處理完了標(biāo)識(shí)棧,接下來需要檢查overflowSystemClasses和overflowACSClasses兩個(gè)變量是否被設(shè)置。如果其中某個(gè)變量被設(shè)置,那么這個(gè)線程就會(huì)試圖獲取對(duì)應(yīng)對(duì)象列表的控制權(quán),并且將這個(gè)變量設(shè)置為NULL。一旦線程獲取了控制權(quán),就會(huì)將引用壓棧到標(biāo)識(shí)棧,一定量的引用被壓棧之后,向本地標(biāo)識(shí)隊(duì)列發(fā)布任務(wù),以允許其他線程輔助進(jìn)行后續(xù)工作。
如果在處理的過程中,再次發(fā)生了標(biāo)識(shí)棧溢出,線程會(huì)記錄在發(fā)生溢出之前處理到的位置,然后重復(fù)上面的工作。
4.1.2 并行標(biāo)識(shí)
由于優(yōu)化的按位清理算法和壓縮避免機(jī)制的存在,使得一個(gè)垃圾收集周期的主要時(shí)間消耗在對(duì)象標(biāo)識(shí)階段。所以,開發(fā)了并行標(biāo)識(shí)技術(shù),該技術(shù)使得在單CPU的主機(jī)上不降低標(biāo)識(shí)的性能,并且能夠在8路主機(jī)上將性能提高4倍左右。
在標(biāo)識(shí)階段的時(shí)間主要消耗在一些輔助線程以及協(xié)調(diào)這些輔助線程共同工作上。一個(gè)線程作為主要線程,也就是我們所說的垃圾收集主線程。這個(gè)線程負(fù)責(zé)掃描C堆棧,找到活動(dòng)的根對(duì)象。在一個(gè)具有N個(gè)CPU的主機(jī)上,會(huì)創(chuàng)建N-1個(gè)輔助線程來輔助完成后續(xù)的標(biāo)識(shí)階段工作。輔助線程的數(shù)量可以由虛擬機(jī)啟動(dòng)參數(shù)
-Xgcthreadsn來重新設(shè)定。設(shè)置為1表示沒有輔助線程,該參數(shù)取值范圍為1到N。
更高層面講,每個(gè)標(biāo)識(shí)線程擁有自己的本地棧以及共享的隊(duì)列,這兩個(gè)變量都包含了那些已經(jīng)被標(biāo)識(shí)但是尚未被掃描的對(duì)象的引用。輔助線程大部分的標(biāo)識(shí)工作都是依賴本地的棧變量來完成的,只有在需要負(fù)載均衡的時(shí)候才會(huì)在共享隊(duì)列上進(jìn)行同步的操作。由于對(duì)于標(biāo)識(shí)比特位的操作是原子操作,所以無需獲取鎖。
由于每個(gè)線程的棧都可以容納4KB個(gè)條目,標(biāo)識(shí)隊(duì)列可以容納2KB個(gè)條目,所以大大降低了標(biāo)識(shí)棧溢出的發(fā)生幾率。
4.1.3 并發(fā)標(biāo)識(shí)
并發(fā)標(biāo)識(shí)機(jī)制保證在堆內(nèi)存增大的時(shí)候,能夠降低垃圾收集的暫停時(shí)間。在堆滿之前,開始進(jìn)行并發(fā)標(biāo)識(shí):垃圾收集器通知每個(gè)線程掃描自己的執(zhí)行棧以查找根對(duì)象,然后基于這些根對(duì)象開始進(jìn)行并發(fā)的追蹤,追蹤是在進(jìn)行鎖堆分配的時(shí)候,由一個(gè)較低優(yōu)先級(jí)的后臺(tái)線程以及全部的應(yīng)用線程一起完成。
由于垃圾收集器在應(yīng)用運(yùn)行的同時(shí)標(biāo)識(shí)活動(dòng)對(duì)象,所以必須記錄已經(jīng)追蹤的對(duì)象的任何變化。為了達(dá)到這個(gè)目的,他采用了一種寫隔離(write barrier)的技術(shù),在對(duì)象發(fā)生變化時(shí)被激活。首先將堆分隔成512字節(jié)的段,每個(gè)段對(duì)應(yīng)卡片表(card table)中的一個(gè)字節(jié)。當(dāng)指向一個(gè)對(duì)象的引用發(fā)生變化時(shí),對(duì)應(yīng)這個(gè)對(duì)象所在段的卡片表中的字節(jié)被設(shè)置為0x01。使用字節(jié)而不是比特位有兩個(gè)原因:字節(jié)的寫入速度比比特位更快,另外,字節(jié)中其他的比特位還可以用作其他用途。
如果發(fā)生以下情況,就會(huì)開始STW(Stop The World)收集:
· 分配失敗
· System.gc()
· 并發(fā)標(biāo)識(shí)階段完成
垃圾收集器開始進(jìn)行并發(fā)標(biāo)識(shí)階段的工作,以試圖在堆耗盡之前完成垃圾收集工作。虛擬機(jī)啟動(dòng)參數(shù)可以管理并發(fā)標(biāo)識(shí)的時(shí)間。
在STW階段,垃圾收集器掃描所有的根對(duì)象,并且通過標(biāo)識(shí)卡片來查看需要進(jìn)一步追蹤的對(duì)象,然后按照普通的模式進(jìn)行清理。這樣能夠保證在并發(fā)標(biāo)識(shí)階段開始時(shí)的不可到達(dá)對(duì)象都可以被清理,但是無法保證在并發(fā)標(biāo)識(shí)階段進(jìn)行過程中變成不可到達(dá)的對(duì)象也被清理。
并發(fā)標(biāo)識(shí)策略可以減少垃圾收集的暫停時(shí)間,但是會(huì)帶來額外消耗,因?yàn)閼?yīng)用線程需要在獲取堆鎖的時(shí)候進(jìn)行一些跟蹤工作。具體的性能降低取決于有多少空閑的CPU時(shí)間可以給后臺(tái)線程使用。另外,寫隔離機(jī)制也會(huì)有額外的消耗。
開啟并發(fā)標(biāo)識(shí)的參數(shù)為:
-Xgcpolicy:<optthruput|optavgpause>
設(shè)置-Xgcpolicy參數(shù)為optthruput禁用并發(fā)標(biāo)識(shí)。optthruput是默認(rèn)設(shè)置。如果你的應(yīng)用中不存在暫停時(shí)間帶來的問題,可以使用這個(gè)默認(rèn)選項(xiàng)獲得最好的吞吐能力。
設(shè)置-Xgcpolicy參數(shù)為optavgpause啟用并發(fā)標(biāo)識(shí)。如果應(yīng)用因?yàn)槔占臅和r(shí)間導(dǎo)致響應(yīng)能力下降,可以使用這個(gè)參數(shù)來改善情況,但是會(huì)降低應(yīng)用吞吐量。
標(biāo)識(shí)階段結(jié)束之后,標(biāo)識(shí)集合中對(duì)于堆中每個(gè)可到達(dá)對(duì)象都進(jìn)行了標(biāo)識(shí),而且是分配集合的子集。清理階段就是計(jì)算分配集合和標(biāo)識(shí)集合的差集,也就是說,找到那些已經(jīng)被分配但是已經(jīng)不被引用的對(duì)象。
最初,使用按位清理(bitsweep)技術(shù),這種技術(shù)檢查標(biāo)識(shí)集合中較長(zhǎng)的連續(xù)0序列,可能對(duì)應(yīng)的就是空閑空間。一旦找到連續(xù)0序列,垃圾收集器檢查這個(gè)序列開始位置對(duì)應(yīng)的對(duì)象的大小,來計(jì)算可以釋放多少空間。如果這個(gè)大小大于512字節(jié)加上對(duì)象頭大小,那么這一段空間就會(huì)重新加入到空閑鏈表中去。
未返回到空閑鏈表中的小的空閑空間,就被稱作是“暗物質(zhì)”,當(dāng)緊鄰“暗物質(zhì)”空間的對(duì)象被釋放之后,或者壓縮動(dòng)作執(zhí)行之后,他就可能再次返回到空閑鏈表中去。垃圾收集器不需要清理空閑段中的每個(gè)對(duì)象,因?yàn)檫@一段都是可以被清理的對(duì)象。在這個(gè)過程中,標(biāo)識(shí)集合會(huì)整個(gè)復(fù)制到分配集合中去,這樣分配集合就表示了堆中所有的已分配對(duì)象。
4.2.1 并發(fā)按位清理技術(shù)
并發(fā)按位清理技術(shù)能夠盡量使用可用的處理器以提高清理階段的執(zhí)行效率。在并發(fā)清理階段,垃圾收集器使用和并發(fā)標(biāo)識(shí)階段同樣的輔助線程,所以,默認(rèn)的參與并發(fā)處理的線程數(shù)也可以由虛擬機(jī)參數(shù)-Xgcghreadsn來設(shè)置。堆被分割成多個(gè)段,段的數(shù)量要遠(yuǎn)遠(yuǎn)大于并發(fā)清理線程的數(shù)量,計(jì)算公式如下:
· 32 * 輔助線程數(shù)量 或者
· 堆的最大大小/16MB
中選擇較大的一個(gè)。每次一個(gè)輔助線程選擇一個(gè)段來掃描,然后按位清理,并且保存每個(gè)段的清理結(jié)果,完成之后,再重新建立空閑鏈表。
在清理階段結(jié)束之后,垃圾收集器可以對(duì)堆中剩余的活動(dòng)對(duì)象進(jìn)行壓縮,以移除他們之間的空閑空間。這個(gè)壓縮過程是非常復(fù)雜的,因?yàn)橐坏┮苿?dòng)了一個(gè)對(duì)象,那么所有指向這個(gè)對(duì)象的引用都需要修改。如果是來自棧的引用,那么垃圾收集器還不能確定這就是一個(gè)對(duì)象引用(有可能碰巧只是一個(gè)浮點(diǎn)數(shù)),就不能移動(dòng)這個(gè)對(duì)象。這樣的對(duì)象還會(huì)繼續(xù)保持在原來的位置,并且dosed標(biāo)識(shí)位會(huì)被設(shè)置。類似的,JNI操作中引用的對(duì)象也是要固化在原來的位置不能被移動(dòng)的,直到JNI操作結(jié)束,不再引用這個(gè)對(duì)象的時(shí)候,才可能被移動(dòng)。利用mptr的低三位設(shè)置為0,垃圾收集器可以在兩個(gè)階段內(nèi)壓縮那些可以移動(dòng)的對(duì)象。其中一位用來標(biāo)識(shí)對(duì)象被清理,這個(gè)清理標(biāo)識(shí)位出現(xiàn)在兩個(gè)地方:size+flags(即OLINK_IsSwapped)以及mptr(即GC_FirstSwapped),這兩種情況中,最低的位(x01)都會(huì)被設(shè)置。
下圖可以幫助你理解壓縮過程:
圖表 10 壓縮階段工作
圖表10展示了壓縮的效果。假設(shè)從A到B是一個(gè)走廊,走廊里面有一些家具(藍(lán)色的塊),代表對(duì)象。空白的區(qū)域代表空閑空間或者“暗物質(zhì)”,有兩個(gè)家具被固定在地板上(交叉紋理塊),代表固化的或者不能被移動(dòng)的對(duì)象。壓縮的過程就類似你要把家具從B推到A,盡可能的靠近A端。但是,不幸的是,你不能把家具舉起來越過固定在地板上的家具,所以,他們右側(cè)的家具最遠(yuǎn)也就推到緊鄰他們的位置。
4.3.1 避免壓縮
圖表 11 荒地
避免壓縮主要是致力于為對(duì)象找到合適的放置位置,以減少或者避免移動(dòng)對(duì)象。避免緊技術(shù)中最主要的一個(gè)概念叫做荒地預(yù)留。荒地預(yù)留技術(shù)試圖在堆中預(yù)留一段空間,然后盡量在其他地方進(jìn)行對(duì)象分配。在堆頂和荒地部分之間,定義一個(gè)邊界。如果存在大對(duì)象的分配,或者上一次垃圾收集之后尚未滿足分配需求的情況下,就會(huì)使用荒地空間。
圖表11是堆中荒地的示意圖。荒地在活動(dòng)的堆空間的最后進(jìn)行分配,初始大小是活動(dòng)堆空間的5%,根據(jù)實(shí)際需求進(jìn)行收縮或者擴(kuò)展。在鎖堆分配失敗的情況下,如果對(duì)象大小小于64KB或者上一次垃圾收集的結(jié)果取得足夠的進(jìn)展,那么就再開始一輪垃圾收集工作。足夠的進(jìn)展的意思是,自從上一次垃圾收集以來,至少有30%的堆空間被占用。30%是默認(rèn)值,可以通過-Xminf參數(shù)設(shè)置。如果沒有取得足夠進(jìn)展,或者對(duì)象大于等于64KB,那么會(huì)嘗試在荒地中進(jìn)行分配。這樣就能夠避免垃圾收集和堆壓縮動(dòng)作。
如果未設(shè)置虛擬機(jī)參數(shù)-Xnocompactgc并且以下幾個(gè)條件任何一個(gè)為true,那么就會(huì)發(fā)生堆壓縮動(dòng)作:
· 設(shè)置了虛擬機(jī)參數(shù)-Xcompactgc
· 清理階段結(jié)束之后,還是無法滿足分配需求
· 調(diào)用System.gc()并且在最后一次分配失敗發(fā)生或者并發(fā)標(biāo)識(shí)收集之前發(fā)生了壓縮動(dòng)作
· TLH消耗了至少一半的存儲(chǔ),并且TLH的平均大小低于1000字節(jié)
· 堆的空閑空間小于5%
· 堆的空閑空間小于128KB
4.3.2 增量壓縮
4.3.2.1 介紹
垃圾收集釋放了對(duì)象空間之后,在堆中就會(huì)產(chǎn)生碎片。那么會(huì)引發(fā)一種現(xiàn)象,堆中還有足夠的空閑空間,但是由于他們是不連續(xù)的,所以無法進(jìn)行后續(xù)的分配。
壓縮動(dòng)作就是用來整理堆中的碎片,他將堆中分散的已分配的存儲(chǔ)段移動(dòng)到堆的一端,那么在堆的另一端就會(huì)生成一個(gè)較大的連續(xù)的空閑空間。但是壓縮會(huì)增加垃圾收集的暫停時(shí)間,對(duì)于1GB的堆,如果進(jìn)行了壓縮動(dòng)作,垃圾收集的暫停時(shí)間可能增加到40秒。對(duì)于應(yīng)用而言,這么長(zhǎng)的暫停時(shí)間通常是不可接受的。增量壓縮技術(shù)就是將壓縮動(dòng)作分散到多次垃圾收集周期中,以減少暫停時(shí)間。
增量壓縮的另一個(gè)重要作用是清理暗物質(zhì)。暗物質(zhì)就是堆中的很小的(小于512字節(jié))存儲(chǔ)片,這些存儲(chǔ)片不在空閑鏈表中,因此不能被重新利用。暗物質(zhì)的存在程度直接影響了應(yīng)用的吞吐能力,因?yàn)樵蕉嗟陌滴镔|(zhì)就會(huì)導(dǎo)致堆中可用的存儲(chǔ)空間越少,可用存儲(chǔ)越少,垃圾收集發(fā)生的頻率就會(huì)越高,對(duì)于應(yīng)用的性能會(huì)產(chǎn)生非常明顯的影響。這些暗物質(zhì)分散在整個(gè)堆中,會(huì)占用堆的大部分空間。
4.3.2.2 增量壓縮概覽
圖表 12 增量壓縮
在圖表12中,涂色的塊表示已經(jīng)分配的空間,未涂色的塊表示空閑空間。暗物質(zhì)會(huì)分布在空閑空間之間。下半部分表示在增量壓縮之后,對(duì)象被移動(dòng)到段的一側(cè),另一側(cè)就整理出了較大的連續(xù)空閑空間。
只有在堆大小大于某個(gè)值(當(dāng)前是128MB)時(shí),才會(huì)發(fā)生增量壓縮。如果堆小于128MB,增量壓縮并不會(huì)帶來比完整壓縮更短的暫停時(shí)間。
增量壓縮有如下兩步:
1) 記錄和標(biāo)識(shí)指向壓縮區(qū)域的所有引用,這個(gè)動(dòng)作在標(biāo)識(shí)階段完成
2) 計(jì)算對(duì)象的新位置,在區(qū)域內(nèi)進(jìn)行壓縮,并修改指向被移動(dòng)對(duì)象的引用
增量壓縮在一個(gè)周期內(nèi)進(jìn)行,一個(gè)增量壓縮周期就是指在垃圾收集周期內(nèi),一次一個(gè)區(qū)域的完成整個(gè)堆的壓縮動(dòng)作。壓縮動(dòng)作會(huì)跨越多個(gè)垃圾收集周期,因此將壓縮動(dòng)作的耗時(shí)分散到多個(gè)垃圾收集周期,減少暫停隨時(shí)間。
4.3.2.3 增量壓縮相關(guān)的主要參數(shù)
默認(rèn)是啟用增量壓縮的,是否運(yùn)行增量壓縮取決于一些觸發(fā)條件。但是有兩個(gè)參數(shù)可以由用戶決定是使用增量壓縮還是傳統(tǒng)壓縮:
· -Xpartialcompactgc,表示每次垃圾收集都使用增量壓縮,除非必須進(jìn)行完整的壓縮動(dòng)作
· -Xnopartialcompactgc,表示禁用增量壓縮機(jī)制
但是,需要提醒的是,-X參數(shù)屬于非標(biāo)準(zhǔn)的虛擬機(jī)參數(shù),可能在未通知的情況下進(jìn)行修改。
4.4 引用對(duì)象
引用對(duì)象能夠使得所有的對(duì)象引用都以同樣的方式被操作和處理,因此垃圾收集器在堆上創(chuàng)建兩個(gè)獨(dú)立的對(duì)象:一個(gè)是對(duì)象本身,一個(gè)是引用對(duì)象。當(dāng)對(duì)象處于不可到達(dá)狀態(tài)時(shí),該引用對(duì)象可以方便的加入到一個(gè)隊(duì)列中去。SoftReference,WeakReference以及PhantomReference由用戶創(chuàng)建,不能修改,即,他們只能指向創(chuàng)建時(shí)的那個(gè)對(duì)象,不能指向其他對(duì)象。帶有finalizer方法的對(duì)象在創(chuàng)建時(shí)注冊(cè)Finalizer類,因此FinalReference對(duì)象指向一個(gè)需要finalize的對(duì)象,并且關(guān)聯(lián)到Finalizer隊(duì)列。
在垃圾收集階段,引用對(duì)象被特殊處理:在標(biāo)識(shí)階段,不會(huì)處理引用對(duì)象的引用字段,標(biāo)識(shí)階段結(jié)束之后,按照如下順序處理引用字段:
1) Soft
2) Weak
3) Final
4) Phantom
對(duì)于SoftReference對(duì)象的處理稍有特殊,如果他所指向的對(duì)象未被標(biāo)識(shí),ST組件會(huì)清理這個(gè)SoftReference。如果存儲(chǔ)不足,那么垃圾收集器根據(jù)最近最常用的規(guī)則來進(jìn)行清理。使用率根據(jù)最后一次被調(diào)用get方法來測(cè)量。一旦一個(gè)引用對(duì)象被處理,他所指向的真實(shí)對(duì)象會(huì)被標(biāo)識(shí),這樣能夠確保如果一個(gè)FinalReference也指向同一個(gè)對(duì)象,他能夠看到這個(gè)標(biāo)識(shí)。FinalReferece不會(huì)被放到處理隊(duì)列中去。因此,在垃圾收集周期可以成功處理引用對(duì)象。
指向未標(biāo)識(shí)對(duì)象的引用對(duì)象最初被放到ReferenceHandler線程的隊(duì)列中,ReferenceHandler線程把對(duì)象從他的隊(duì)列中移除,同時(shí)查看這個(gè)對(duì)象自己的隊(duì)列是否存在,如果存在,則這個(gè)對(duì)象被重新放入ReferenceHandler線程的隊(duì)列中去,以進(jìn)行后續(xù)的處理。因此,F(xiàn)inalReference會(huì)重新入隊(duì),確保最后finalize方法被finalizer線程執(zhí)行。
4.4.1 JNI Weak引用
JNI Weak引用對(duì)象提供類似WeakReference對(duì)象的功能,但是處理機(jī)制是不同的。一段JNI代碼可以創(chuàng)建或者刪除JNI Weak引用對(duì)象,這個(gè)對(duì)象指向一個(gè)真正的對(duì)象。當(dāng)引用對(duì)象指向的對(duì)象未被標(biāo)識(shí),垃圾收集器會(huì)清理這個(gè)引用對(duì)象,但是不同于前一章提到的隊(duì)列機(jī)制。需要注意的是,如果清理JNI Weak引用對(duì)象失敗,可能會(huì)引發(fā)內(nèi)存泄漏以及性能方面的問題。對(duì)于全局的JNI引用,也是同樣的處理方式。JNI Weak引用對(duì)象最后會(huì)被引用對(duì)象處理線程處理,因此,對(duì)于一個(gè)已經(jīng)被finalize的對(duì)象,如果存在指向他的phantom引用,他的JNI Weak引用對(duì)象也會(huì)持續(xù)存在。
4.5 堆擴(kuò)展
堆擴(kuò)展發(fā)生在垃圾收集完畢并且所有線程重新啟動(dòng)之后,但是HEAP_LOCK尚被持有的情況下。如果滿足以下條件之一,堆的活動(dòng)空間會(huì)進(jìn)行擴(kuò)展,直到堆的最大限制:
· 垃圾收集無法釋放足夠的空間來滿足分配需求
· 空閑空間低于最小空閑空閑設(shè)定,-Xminf參數(shù),默認(rèn)30%
· 垃圾收集占用了超過13%的時(shí)間,并且按照最小擴(kuò)展量(-Xmine)擴(kuò)展之后,還是無法滿足堆內(nèi)最大空閑空間(-Xmaxf)要求的。
每次擴(kuò)展的量計(jì)算規(guī)則如下:
· 如果由于無法滿足堆的空閑空間為-Xminf(30%),垃圾收集器計(jì)算能夠滿足-Xminf堆空閑的擴(kuò)展量。如果這個(gè)計(jì)算結(jié)果大于最大擴(kuò)展量-Xmaxe(默認(rèn)為0,即沒有最大擴(kuò)展量限制),那么會(huì)采用-Xmaxe設(shè)定的數(shù)量。如果計(jì)算結(jié)果小于-Xmine(默認(rèn)為1MB),那么會(huì)按照-Xmine設(shè)定進(jìn)行擴(kuò)展
· 如果由于垃圾收集之后仍然不能滿足分配需求,并且垃圾收集的時(shí)間占用總運(yùn)行時(shí)間的比例不超過13%,則按照實(shí)際的分配需求來擴(kuò)展
· 如果由于其他原因觸發(fā)擴(kuò)展,則垃圾收集器計(jì)算能夠滿足17.5%堆空閑的擴(kuò)展量。類似上面,根據(jù)-Xmaxe和-Xmine的設(shè)定進(jìn)行調(diào)整
· 最終,如果垃圾收集未能釋放出滿足分配需求的空間,則堆的擴(kuò)展要保證至少滿足分配需求
所有計(jì)算出的擴(kuò)展量,在32位架構(gòu)上是64KB的整倍,在64位架構(gòu)上是4MB的整倍。
堆收縮發(fā)生在垃圾收集完畢并且所有線程都處于掛起狀態(tài)時(shí)。如果滿足以下任何一個(gè)條件,就不會(huì)發(fā)生堆收縮:
· 垃圾收集未能釋放滿足分配需求的空閑空間
· -Xmaxf參數(shù)設(shè)定為100%(默認(rèn)是60%)
· 在最近的3次垃圾收集之中發(fā)生過堆擴(kuò)展
· 由于調(diào)用System.gc()發(fā)生的垃圾收集,并且在收集周期開始之前,堆的空閑空間小于-Xminf(30%)
如果以上條件都不滿足,并且存在大于-Xmaxf的空閑空間,垃圾收集器會(huì)計(jì)算收縮量,以保證能夠擁有-Xmaxf的空閑空間,并且不小于初始大小(-Xms)。計(jì)算出的收縮量在32位架構(gòu)上是64KB的整倍,在64位架構(gòu)上是4MB的整倍。
如果滿足以下任何一個(gè)條件,會(huì)在收縮之前發(fā)生壓縮:
· 在本次垃圾收集周期未進(jìn)行壓縮
· 堆尾沒有空閑存儲(chǔ)片,或者堆尾的空閑存儲(chǔ)片小于需要收縮量的10%
· 在上一次垃圾收集周期中未發(fā)生壓縮和收縮
從1.3.0以后,引入了Resettable JVM,該虛擬機(jī)只能運(yùn)行在z/OS平臺(tái)。在http://www.s390.ibm.com/Java有詳細(xì)的文檔
posted on 2008-04-22 11:29
YODA 閱讀(2182)
評(píng)論(0) 編輯 收藏