原文出處:卡表和寫屏障工做原理html
問題描述數組
爲了深刻了解GC運行機制,我正在閱讀一些相關材料。偶然發現叫作「card table」的字眼,我Google它,可是沒有找到讓我信服的信息。絕大多數的解釋很膚淺模糊。jvm
個人問題:指針
卡表和寫屏障如何工做?code
卡表裏面記錄了什麼?htm
垃圾收集器怎麼知道特定的對象被其餘下一代中存活對象引用?對象
我已經火燒眉毛,想獲得一些關於這些問題實際性的知識。blog
回答1索引
我不肯定你是否是看到了巨low的描述仍是想知道更多細節,我很滿意已經看到的解釋。若是描述得很簡練,辣麼由於它的機制真的簡單。內存
你顯然已經知道,分代垃圾收集器須要掃描被老對象引用的新對象,掃描全部的老對象看起來很合適,可是破壞了某些分代的優點,因此你須要慢慢淡忘它。無論怎樣,你須要寫屏障-每當一些變量(引用數據類型)被寫入或者被分配。若是一個新的引用指向一個新生代對象並且該引用存儲在老年代對象中,寫屏障爲了垃圾收集而進行記錄。關鍵在於它如何記錄,標準方案是使用被叫作 remembered sets,一個存儲全部存放一個(或者數個)指向新生代對象引用的老年代對象。你能夠想象一下,它確實一點空間。
卡表是一種取捨: 告訴你一對象包含指向新生帶對象引用(或者至少在某一時刻已經操做完成),它將對象分組存到一個固定尺寸的區域而且可索引到這些老年對象。這樣固然下降了空間使用率。可是很是正確是,你不須要關心如何存儲這些對象,只要始終關注卡表。爲了效率,你能夠根據內存地址將這些老年代對象組織起來(由於能夠很方便獲取到),花大力氣將二者區分開來(按位操做去區分)。
並且不用維護一個區域列表,你也不用預先分配一個爲了可能出現的區域而準備的空間。特別指出,它是一個N字節的字節數組,N是容器的數量,因此若是第i容器沒有包含新生代對象,辣麼第i個元素的值是0。 卡表用在這中狀況下很合適。有表明性的是這個空間的分配,並且由堆釋放。若是它不須要擴展的話,甚至被內置在內存的最初時期。除非所有空間被劃分爲堆(很是罕見),超過了根據start_of_memory_region >> K公式計算得出數字代替0。因此爲了根據idnex進入卡表,你必須減掉堆開始的內存地址。
總之,寫屏障發現了「some_obj.field = other_obj;」相似的聲明,在老年代對象裏面存儲一個新生代對象的引用,它是這樣作的:
card_table[(&old_obj - start_of_heap) >> K] = 1;
上面的 &old_obj是如今擁有新生代對象指針老年對象的內存地址(當被斷定指向新生對象的引用的時候已經進行註冊)。minor GC 期間,垃圾收集器查看卡表爲依據,掃描由於擁有新生代對象指針的堆區:
for i from 0 to (heap_size >> K): if card_table[i]: scan heap[i << K .. (i + 1) << K] for young pointers
回答2
前一段時間我寫一篇關於解釋HotSpot JVM新生代垃圾收集機制的文章Understanding GC pauses in JVM, HotSpot's minor GC。
寫屏障髒卡的原理很是簡單,每當程序在內存中修改引用的時候,將修改的內存頁設置爲髒,JVM中卡表很特殊,每512Byte大的內存頁與卡表中一個Byte關聯。 通常垃圾收集老年代中引用新生代對象的對象須要掃描整個老年代。這是須要卡表的緣由。自寫屏障清空後新生代全部對象都須要被建立(或者遷移),所以不是髒內存頁沒有指向新生代的引用。這意味咱們僅僅能夠掃描髒內存頁中的對象。