IBM JVM的GC分為三個步驟
Mark phase(標記)
Sweep phase(清掃)
Compaction phase(內存緊縮)
在了解這些過程之前
我們先看一下IBMJava中的對象的Layout和Heap lay out 一個Java對象在IBM vm中的結構如下
size+flags
mptr
locknflags
objectdata
size+flags
這是一個
byte的slot(
平台)
這個slot的主要功能就是描述對象的尺寸
由於IBMJava中的對象都是以
byte的倍數分配的
因此對象的尺寸其實就是真實尺寸/
存放在
byte的slot中
另外在這個slot的低三位是保留字段起到標記對象的作用
他們分別為 bit
:swapped bit
這個交換位被用於Compaction phase即內存緊縮階段使用
同時 這一位在標記堆棧溢出的時候(mark stack overflow)也被用於標記NotYetScanned狀態
bit
dosed bit
這個位用於標示這個對象是否被某個堆棧或者寄存器reference到了
如果這個標志被至位則這個對象就不能在當前的GC cycle中被刪除
而且如果某個reference指向的內存不是一個真實的reference比如是一個簡單的float 或者integer變量但是它的值恰巧就是Heap中某個Object的地址的時候
我們就不能修改這個refernece
這種對象的bit
也被置為
bit
:pinned bit
標記一個對象是否是一個一個釘扣對象(PINNED object)
一個Pinned Object也不能被GC刪除
因為他們可能在Heap之外被reference到了
典型的一個例子就是Thread
還記得我上面說的僵死縣城麼?它不能被刪除的道理就是這個
另外一種PinnedObject就是 JNI Object
即被本地代碼使用的對象
Mptr:
在
平台上也是
byte的slot
Mptr有兩個功能
如果mptr不是一個數組
則Mptr指向一個方法塊(method block)
你可以通過這個method block來得到一個類塊(class block)
這個類塊
告訴你這個Object是屬於哪個class的實例
method block和class block由Class Loader分配
而不是heap在heap中進行分配
如果mptr是一個數組(Array)
mptr包含了這個對象中
數組的元素個數
lockflags
在
平台上也是
byte的slot
但是這個slot只有低
位被用到
bit
:是array flag
如果這個位被置位
那麼這個對象就是一個數組同時mptr字段就包含了數組的元素個數
bit
是hashed和moved bit
如果這個位被置位
那麼他就告訴我們這個對象在被hashed以後被刪除了
Object Data
就是這個對象本身的數據
Heap layout:
heap top
heap limit
heap base
heap base是heap的起始地址
heap top是heap的結束地址
heaplimit 是當前程序使用的那段heap可以進行擴展和收縮的極限
你可以用
Xmx參數在java運行的時候對heap top和heap base進行控制
Alloc bits 和 mark bits
heap top allocmax markemax
heap limit alloc size marksize
heap base
上面這個結構描述了heap和alloc bits 以及
markbits之間的關系
allocbits和markbits都是元素為
個bit的vector
他們與heap有同樣的長度
下面是兩個對象被分配以後在heap和兩個vector中的表現
heaptop allocmax markmax
heaplimit allocsize marksize
object
top
object
base object
allocbit object
markbit
object
top
object
base object
allocbit
如上面的結構
如果一個對象在heap被alloc出來
那麼在allocbits中就標示出這個對象的起始地址所在的地址
allocbits中只標記起始地址
但是這個過程告訴我們這個對象在那裡被創建
但是不告訴我們這個對象是否存活
當在mark phase中如果某一個對象比如object
仍然存活
那麼就在markbits中對應的地址上標記一下The free list
IBM jvm中的空閒塊用用一個free list鏈標示
如圖
freechunck
freechunck
freechunckn
size size size
next
>next
>
next
>NULL
freeStorage freeStorage freestorge
有了這些基本概念我們來看看Mark phase的工作情況
MarkPhase
GC的Mark phase將標記所有還活著的對象
這個標記所有可達對象的過程稱為tracing
Jvm的活動狀態(active state)是由下面幾個部分組成的
每個線程的保存寄存器(saved registers)
描述線程的堆棧
Java類中的靜態元素
以及局部和全局的JNI(Java Native Interface)引用
在Jvm中的方法調用都在C Stack上引發一個Frame
這個Frame包含了
對象實例
為局部變量的assignment結果或者傳入方法的參數
所有這些引用在Tracing過程中都被同等對待
實際上
我們可以把一個線程的堆棧看城一系列
bytes slot的集合
然後對每一個堆棧都從頂向下對這些slot進行掃描
在掃描的過程中都必須校驗每個slot是否指向heap當中的一個真實的對象
因為在前面我就說過
很有可能這些slot值僅僅是一個int或float但是他們的值恰巧就等於heap中的一個對象地址
因此在掃描的時候必須相當的保守
掃描的時候必須保證所有的指針都是一個對象
而且這個對象沒有在GC中被刪除
只有符合下面條件的slot才是一個指向對象的指針
必須以
byte的倍數分配的內存
必須在heap的范圍之內(即大於heapbase小於heaptop)
對應的allocbit必須置為
滿足這些條件的對象引用我們稱為roots
並且把他們的dosed bit置為
表示不能被GC刪除
我想大家已經知道C#中為何連Int和Float都是OBject的原因了吧
在C#中因為都是OBject因此
在tracing的過程中就減少了一次校驗
這個減少對性能起到很大的影響
如果掃描完成
那麼Tracing過程便能安全精確的執行
也就是說我們可以在roots中通過reference找到他對應的objects
由於他們是真實的reference
那麼我們就能夠在compactionphase中移動對應的對象並且修改這些reference
Trace過程使用了一個可以容納
k的slots的stack
所有的引用逐個push進入這個堆棧並且同時在markbits中進行標記
當push和mark的工作完成之後
我們開始pop出這些slot並且進行trace
常規的對象(非數組對象)將通過mptr去訪問classblock
classblock將會告訴我們從這個對象中找到的其他對象的reference在那裡?當我們在classblock找到一個refernce以後
如果發現他沒有被mark
那麼我們就在markallocbits中mark他然後把他再壓入堆棧
數組對象利用mptr去訪問每個數組元素
如果他們沒有mark則mark然後壓入堆棧
Trace過程一直持續進行
直到堆棧為空
MarkStack OverFlow
由於markStack限制了尺寸
因此它可能會溢出
如果溢出發生
那麼我們就設定一個全局的標志來表明發生了MarkStack OverFlow
然後我們將那些不能push入stack的OBject的bit
設定為NotYetScanned
然後當tracing過程完成以後
檢驗全局標志如果發現有overflow則把NotYetScanned的對象再次壓入堆棧開始新的tracing過程
並行Mark(Parallel Mark)
由於使用逐位清掃(bitwise sweep)和內存緊縮規避功能
GC將化大部分的時間是用於Mark而非前面兩項
這就導致了IBM JVM需要開發一個GC的並行版本
並行GC的目的不是以犧牲單CPU系統上的效能來換取在
路對稱CPU系統上的高效率
並行Mark的基本思想就是通過多個輔助線程(helper thread)和一個共享工作的工具來減少Marking的時間
在單CPU系統中
執行GC工作的只有一個主線程
Parallel mark仍然需要這個主線程的參與
他充當了管理協調的角色
這個Thread所要執行的工作和單CPU上的一樣多
包括他必須掃描C
Stack來鑒別需要收集的roots指針
一個有N路對稱CPU的系統自動含有n
個helper thread並且平均分布在每個CPU上
master thread將scan完的reference集合進行分塊
然後交給helper thread獨立完成mark工作
每個Helper thread都被分配了一個獨立的本地mark stack
以及一個shareable queue
sharqueue將存放help thread在mark overflow的時候的NotyetScanned對象
然後由master thread將sharequeue中的對象balance到其他已經空閒的thread上去
並發Mark(Concurrent mark)
Concurrent mark的主要目的在於當heap增長的時候減少GC的pause time
只要heap到達heap limit的時候
Concurrent mark就會被執行
在Concurrent phase中
GC要求應用中的每個線程(不是指helper thread而是應用程序自己開啟的線程以便充分利用系統資源)掃描他們自己的堆棧來得到roots
然後使用這些roots來同步的trace 可達對象
Tracing工作是由一個後台的低優先級的線程執行
同時程序自己開啟的線程在分配內存的時候必須執行heap lock allocation
由於使用程序自己開啟的線程並發的執行mark live objects
我們必須紀錄那些已經trace過的object的變化
這個功能是采用一個叫寫閘(write barrier) 來實現的
這個寫閘在每次改變引用的時候被激活
它告訴我們什麼時候一個對象被跟新過了
以便我們從新掃描那部分heap
寫閘的具體實現是Heap會分配出
byte的內存段每個段都分配了一個byte在卡表中(card table)
無論何時一個對象的reference被更新cardtable將同步紀錄這個對象的起始地址
使用Byte而不用bit的原因是寫byte要比寫bit快
倍
而且我們可能希望空余的bit會在未來被用到
當Concurrent mark執行完畢以後
STW collection(stop total world)將會被執行
stw的意思是指suspend所有程序自己開啟的線程
因此我們可以看到如果使用Concurrent mark那
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19555.html