熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

IBM Java如何做到高性能GC的實現內幕

2022-06-13   來源: JSP教程 

  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狀態 bitdosed 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的slotMptr有兩個功能
  如果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
  
  objecttop
  
  
  objectbase objectallocbit objectmarkbit
  
  objecttop
  
  objectbase objectallocbit
  
  如上面的結構如果一個對象在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將標記所有還活著的對象這個標記所有可達對象的過程稱為tracingJvm的活動狀態(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去訪問classblockclassblock將會告訴我們從這個對象中找到的其他對象的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上的一樣多包括他必須掃描CStack來鑒別需要收集的roots指針一個有N路對稱CPU的系統自動含有n個helper thread並且平均分布在每個CPU上master thread將scan完的reference集合進行分塊然後交給helper thread獨立完成mark工作
  
  每個Helper thread都被分配了一個獨立的本地mark stack以及一個shareable queuesharqueue將存放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
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.