1. 程式人生 > >JVM之卡表(Card Table)

JVM之卡表(Card Table)

我們知道,JVM在進行垃圾收集時,需要先標記所有可達物件,然後再清除不可達物件,釋放記憶體空間。那麼,如何快速的找到所有可達物件呢?

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

簡單粗暴的實現方式,通常都是不可取的。那JVM是如何實現快速標記可達物件的?

答案是GC Roots。

GC Roots是垃圾收集器尋找可達物件的起點,通過這些起始引用,可以快速的遍歷出存活物件。GC Roots最常見的是靜態引用和堆疊的區域性引用變數。然而,這不是我們這講的重點:)

現代JVM,堆空間通常被劃分為新生代和老年代。由於新生代的垃圾收集通常很頻繁,如果老年代物件引用了新生代的物件,那麼,需要跟蹤從老年代到新生代的所有引用,從而避免每次YGC時掃描整個老年代,減少開銷。

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

卡表(Card Table)

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

卡表(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、1、2對應的卡頁起始地址分別為0、512、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 虛擬機器》鄭雨迪

個人公眾號

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

二進位制之路