JVM之卡表(Card Table)

咱們知道,JVM在進行垃圾收集時,須要先標記全部可達對象,而後再清除不可達對象,釋放內存空間。那麼,如何快速的找到全部可達對象呢?html

最簡單粗暴的實現,就是每次進行垃圾收集時,都對整個堆中的全部對象進行掃描,找到全部存活對象。邏輯是簡單,但性能比較差。java

簡單粗暴的實現方式,一般都是不可取的。那JVM是如何實現快速標記可達對象的?數組

答案是GC Roots。緩存

GC Roots是垃圾收集器尋找可達對象的起點,經過這些起始引用,能夠快速的遍歷出存活對象。GC Roots最多見的是靜態引用和堆棧的局部引用變量。然而,這不是咱們這講的重點:)bash

現代JVM,堆空間一般被劃分爲新生代和老年代。因爲新生代的垃圾收集一般很頻繁,若是老年代對象引用了新生代的對象,那麼,須要跟蹤從老年代到新生代的全部引用,從而避免每次YGC時掃描整個老年代,減小開銷。併發

對於HotSpot JVM,使用了卡標記(Card Marking)技術來解決老年代到新生代的引用問題。具體是,使用卡表(Card Table)和寫屏障(Write Barrier)來進行標記並加快對GC Roots的掃描。oracle

卡表(Card Table)

基於卡表(Card Table)的設計,一般將堆空間劃分爲一系列2次冪大小的卡頁(Card Page)。jvm

卡表(Card Table),用於標記卡頁的狀態,每一個卡表項對應一個卡頁。高併發

HotSpot JVM的卡頁(Card Page)大小爲512字節,卡表(Card Table)被實現爲一個簡單的字節數組,即卡表的每一個標記項爲1個字節。性能

當對一個對象引用進行寫操做時(對象引用改變),寫屏障邏輯將會標記對象所在的卡頁爲dirty。

OpenJDK/Oracle 1.6/1.7/1.8 JVM默認的卡標記簡化邏輯以下:

CARD_TABLE [this address >> 9] = 0;
複製代碼

首先,計算對象引用所在卡頁的卡表索引號。將地址右移9位,至關於用地址除以512(2的9次方)。能夠這麼理解,假設卡表卡頁的起始地址爲0,那麼卡表項0、一、2對應的卡頁起始地址分別爲0、5十二、1024(卡表項索引號乘以卡頁512字節)。

其次,經過卡表索引號,設置對應卡標識爲dirty。

帶來的2個問題

1.無條件寫屏障帶來的性能開銷

每次對引用的更新,不管是否更新了老年代對新生代對象的引用,都會進行一次寫屏障操做。顯然,這會增長一些額外的開銷。可是,與YGC時掃描整個老年代相比較,這個開銷就低得多了。

不過,在高併發環境下,寫屏障又帶來了虛共享(false sharing)問題。

2.高併發下虛共享帶來的性能開銷

在高併發狀況下,頻繁的寫屏障很容易發生虛共享(false sharing),從而帶來性能開銷。

假設CPU緩存行大小爲64字節,因爲一個卡表項佔1個字節,這意味着,64個卡表項將共享同一個緩存行。

HotSpot每一個卡頁爲512字節,那麼一個緩存行將對應64個卡頁一共64*512=32KB。

若是不一樣線程對對象引用的更新操做,剛好位於同一個32KB區域內,這將致使同時更新卡表的同一個緩存行,從而形成緩存行的寫回、無效化或者同步操做,間接影響程序性能。

一個簡單的解決方案,就是不採用無條件的寫屏障,而是先檢查卡表標記,只有當該卡表項未被標記過纔將其標記爲dirty。

這就是JDK 7中引入的解決方法,引入了一個新的JVM參數-XX:+UseCondCardMark,在執行寫屏障以前,先簡單的作一下判斷。若是卡頁已被標識過,則再也不進行標識。

簡單理解以下:

if (CARD_TABLE [this address >> 9] != 0)
  CARD_TABLE [this address >> 9] = 0;
複製代碼

與原來的實現相比,只是簡單的增長了一個判斷操做。

雖然開啓-XX:+UseCondCardMark以後多了一些判斷開銷,可是卻能夠避免在高併發狀況下可能發生的併發寫卡表問題。經過減小併發寫操做,進而避免出現虛共享問題(false sharing)。

也用於CMS GC

CMS在併發標記階段,應用線程和GC線程是併發執行的,所以可能產生新的對象或對象關係發生變化,例如:

  • 新生代的對象晉升到老年代;
  • 直接在老年代分配對象;
  • 老年代對象的引用關係發生變動;
  • 等等。

對於這些對象,須要從新標記以防止被遺漏。爲了提升從新標記的效率,併發標記階段會把這些發生變化的對象所在的Card標識爲Dirty,這樣後續階段就只須要掃描這些Dirty Card的對象,從而避免掃描整個老年代。

參見:Java之CMS GC的7個階段


參考

psy-lob-saw.blogspot.com/2014/10/the…

blogs.oracle.com/dave/false-…

bibliography.selflanguage.org/_static/wri…

www.memorymanagement.org/glossary/b.…

www.memorymanagement.org/glossary/w.…

docs.oracle.com/cd/E19205-0…

ifeve.com/falsesharin…

《深刻拆解 Java 虛擬機》鄭雨迪

我的公衆號

更多文章,請關注公衆號:二進制之路

二進制之路
相關文章
相關標籤/搜索