1. 程式人生 > >GC基本演算法及C++GC機制

GC基本演算法及C++GC機制

前言

垃圾收集器是一種動態儲存分配器,它自動釋放程式不再需要的已分配的塊,這些塊也稱為垃圾。在程式設計師看來,垃圾就是不再被引用的物件。自動回收垃圾的過程則稱為垃圾收集(garbage collection)。在一個支援垃圾收集的語言中,程式顯式地申請記憶體,但從不需要顯式的釋放它們。垃圾收集器會定期識別垃圾塊,並將垃圾塊放回空閒連結串列中。顯然,C語言的malloc包不是一個帶GC功能的分配器,程式設計師顯式 呼叫malloc分配記憶體,也需要顯式呼叫free釋放它。而像java、C#這些語言等則提供了垃圾收集器。這篇文章的內容為介紹一些常用的GC演算法,同時簡單提一下C++的GC機制。

基本概念

有向可達圖與根集

垃圾收集器將儲存器視為一張有向可達圖。圖中的節點可以分為兩組:一組稱為根節點,對應於不在堆中的位置,這些位置可以是暫存器、棧中的變數,或者是虛擬儲存器中讀寫資料區域的全域性變數;另外一組稱為堆節點,對應於堆中一個分配塊,如下圖:

當存在一個根節點可到達某個堆節點時,我們稱該堆節點是可達的,反之稱為不可達。不可達堆節點為垃圾。可見垃圾收集的目標即是從從根集出發,尋找未被引用的堆節點,並將其釋放。

三種基本的垃圾收集演算法及其改進演算法

垃圾收集演算法是一個重要而活躍的研究領域,自從20世紀60年代開始對垃圾收集進行研究以來,垃圾演算法的研究從未停止。常見的垃圾收集演算法有一下這幾種型別:

1、引用計數演算法

引用技術演算法是唯一一種不用用到根集概念的GC演算法。其基本思路是為每個物件加一個計數器,計數器記錄的是所有指向該物件的引用數量。每次有一個新的引用指向這個物件時,計數器加一;反之,如果指向該物件的引用被置空或指向其它物件,則計數器減一。當計數器的值為0時,則自動刪除這個物件。這個思路可以參考C++ 引用計數技術及智慧指標的簡單實現

引用計數演算法的優點是實現簡單,在原生不支援GC的語言中也能容易實現出來。另一個優點這種垃圾收集機制是即時回收,也即是物件不再被引用的瞬間就立即被釋放掉。而其缺點是若存在物件的迴圈引用,無法釋放這些物件,例圖:

缺點二是多個執行緒同時對引用計數進行增減時,引用計數的值可能會產生不一致的問題,必須使用併發控制機制解決這一問題,也是一個不小的開銷。

2、 Mark & Sweep 演算法

這個演算法也稱為標記清除演算法,為McCarthy獨創。它也是目前公認的最有效的GC方案。Mark&Sweep垃圾收集器由標記階段和回收階段組成,標記階段標記出根節點所有可達的對節點,清除階段釋放每個未被標記的已分配塊。典型地,塊頭部中空閒的低位中的一位用來表示這個塊是否已經被標記了。通過Mark&Sweep演算法動態申請記憶體時,先按需分配記憶體,當記憶體不足以分配時,從暫存器或者程式棧上的引用出發,遍歷上述的有向可達圖並作標記(標記階段),然後再遍歷一次記憶體空間,把所有沒有標記的物件釋放(清除階段)。因此在收集垃圾時需要中斷正常程式,在程式涉及記憶體大、物件多的時候中斷過程可能有點長。當然,收集器也可以作為一個獨立執行緒不斷地定時更新可達圖和回收垃圾。該演算法不像引用計數可對記憶體進行即時回收,但是它解決了引用計數的迴圈引用問題,因此有的語言把引用計數演算法搭配Mark & Sweep 演算法構成GC機制。

3、 節點複製演算法

Mark & Sweep演算法的缺點是在分配大量物件時,且物件大都需要回收時,回收中斷過程可能消耗很大。而節點複製演算法則剛好相反,當需要回收的物件越多時,它的開銷很小,而當大部分物件都不需要回收時,其開銷反而很大。
演算法的基本思路是這樣的:從根節點開始,被引用的物件都會被複制到一個新的儲存區域中,而剩下的物件則是不再被引用的,即為垃圾,留在原來的儲存區域。釋放記憶體時,直接把原來的儲存區域釋放掉,繼續維護新的儲存區域即可。過程如圖:

可以看到,當被引用物件(非垃圾物件)很多時,需要複製很多的物件到新儲存區域。

分代回收

以上三種基本演算法各有各的優缺點,也各自有許多改進的方案。通過對這三種方式的融合,出現了一些更加高階的方式。而高階GC技術中最重要的一種為分代回收。它的基本思路是這樣的:程式中存在大量的這樣的物件,它們被分配出來之後很快就會被釋放,但如果一個物件分配後相當長的一段時間內都沒有被回收,那麼極有可能它的生命週期很長,嘗試收集它是無用功。為了讓GC變得更高效,我們應該對剛誕生不久的物件進行重點掃描,這樣就可以回收大部分的垃圾。為了達到這個目的,我們需要依據物件的”年齡“進行分代,剛剛生成不久的物件劃分為新生代,而存在時間長的物件劃分為老生代,根據實現方式的不同,可以劃分為多個代。

一種回收的實現策略可以是:首先從根開始進行一次常規掃描,掃描過程中如果遇到老生代物件則不進行遞迴掃描,這樣可大大減少掃描次數。這個過程可使用標記清除演算法或者複製收集演算法。然後,把掃描後殘留下來的物件劃分到老生代,若是採用標記清除演算法,則應該在物件上設定某個標誌位標誌其年齡;若是採用複製收集,則只需要把新的儲存區域內物件設定為老生代就可以了。而實際的實現上,分代回收演算法的方案五花八門,常常會融合幾種基本演算法。

而其他的改進演算法數量非常龐大,但大都基於上述的三種基本演算法。

C++垃圾回收機制

C語言本身沒有提供GC機制,而C++ 0x則提供了基於引用計數演算法的智慧指標進行記憶體管理。也有一些不作為C++標準的垃圾回收庫,如著名的Boehm庫。藉助其他的演算法也可以實現C/C++的GC機制,如前面所說的標記清除演算法。

當應用程式使用malloc試圖從堆上獲得記憶體塊時,通常都是以常規方式來呼叫malloc,而當malloc找不到合適空閒塊的時候,它就會去呼叫垃圾收集器,以回收垃圾到空閒連結串列。此時,垃圾收集器將識別出垃圾塊,並通過free函式將它們返回給堆。這樣看來,垃圾收集器代替我們呼叫了free函式,從而讓我們顯式分配,而無須顯式釋放。

上圖中的垃圾收集器為一個保守的垃圾收集器。保守的定義是:每個可達的塊都能夠正確地被標記為可達,而一些不可達塊卻可能被錯誤地標記為可達。其根本原因在於C/C++語言不會用任何型別資訊來標記儲存器的位置,即對於一個整數型別來說,語言本身沒有一種顯式的方法來判斷它是一個整數還是一個指標。因此,如果某個整數值所代表的地址恰好的某個不可達塊中某個字的地址,那麼這個不可達塊就會被標記為可達。所以,C/C++所實現的垃圾收集器都不是精確的,存在著回收不乾淨的現象。而像JAVA的垃圾收集器則是精確回收。在《關於C++ 0x 裡垃圾收集器的講座》這篇文章裡提到,C++標準提案中使用gc_strict、 gc_relax這樣的關鍵字來描述一個記憶體區內有沒有指標,但無法精確到每個資料上。實際上,早在07年,一份C++標準提案N2670就提出要將垃圾回收機制作為加入C++,最後提案是沒有通過,其原因大概是因為實現複雜,由於語言本身原因存在這樣那樣的限制。所以在C++ 0x中除了shard_ptr、weak_ptr這些智慧指標外,我們並沒看看到GC機制的身影。而至於C++是如何解決引用計數的迴圈引用問題以及併發控制問題,我們將以另外一篇文章進行介紹。

(完)

參考書籍

深入理解計算機系統 [美]Randal E.Bryant / David O'Hallaron 機械工業出版社

程式碼的未來 [日] 松本行弘 人民郵電出版社

相關推薦

GC基本演算法C++GC機制

前言 垃圾收集器是一種動態儲存分配器,它自動釋放程式不再需要的已分配的塊,這些塊也稱為垃圾。在程式設計師看來,垃圾就是不再被引用的物件。自動回收垃圾的過程則稱為垃圾收集(garbage collection)。在一個支援垃圾收集的語言中,程式顯式地申請記憶體,但從不需要顯式的釋放它們。垃圾收集器會定期識別垃圾

順序表的基本操作C語言完整實現

對順序表進行操作,大致可分為以下幾類: 表的建立; 表中新增(新增)資料元素; 表中刪除資料元素; 表中查詢指定資料元素; 表中更改某資料元素; 以上操作各自的原理及實現如下所示。 順序表的建立 順序表的建立,也就是順序表進行初始化,在預先申請記憶體空間的同時,給變數 size 和 len

鏈佇列的基本操作C語言實現

佇列,可以理解為遵循“先進先出”原則的線性表,即資料元素依次從表的一端進,從表的另一端出。 鏈佇列,即用鏈式的儲存結構(連結串列)實現的佇列,其實現思想是:用連結串列的表頭一端表示佇列的隊頭,另一端表示佇列的隊尾(實現程式碼更加簡單)。 反過來的話,當佇列增加元素時,要採用頭插法,在刪除資料元素的時候,需

GC詳解Minor GC和Full GC觸發條件總結

GC,即就是Java垃圾回收機制。目前主流的JVM(HotSpot)採用的是分代收集演算法。與C++不同的是,Java採用的是類似於樹形結構的可達性分析法來判斷物件是否還存在引用。即:從gcroot開始,把所有可以搜尋得到的物件標記為存活物件。 GC機制 要準確理解Jav

二叉樹後序遍歷(遞迴與非遞迴)演算法C語言實現

二叉樹後序遍歷的實現思想是:從根節點出發,依次遍歷各節點的左右子樹,直到當前節點左右子樹遍歷完成後,才訪問該節點元素。 圖 1 二叉樹   如圖 1 中,對此二叉樹進行後序遍歷的操作過程為: 從根節點 1 開始,遍歷該節點的左子樹(以節點 2 為根節點); 遍歷節點 2 的左子樹(以節點 4 為根

二叉樹中序遍歷(遞迴和非遞迴)演算法C語言實現

二叉樹中序遍歷的實現思想是: 訪問當前節點的左子樹; 訪問根節點; 訪問當前節點的右子樹; 圖 1 二叉樹   以圖  1 為例,採用中序遍歷的思想遍歷該二叉樹的過程為: 訪問該二叉樹的根節點,找到 1; 遍歷節點 1 的左子樹,找到節點 2; 遍歷節點 2 的左子樹,找到節點 4;

雜湊查詢演算法C語言實現

上一節介紹了有關雜湊表及其構造過程的相關知識,本節將介紹如何利用雜湊表實現查詢操作。 在雜湊表中進行查詢的操作同雜湊表的構建過程類似,其具體實現思路為:對於給定的關鍵字 K,將其帶入雜湊函式中,求得與該關鍵字對應的資料的雜湊地址,如果該地址中沒有資料,則證明該查詢表中沒有儲存該資料,查詢失敗:如果雜湊地址中

插入排序演算法C語言實現

插入排序演算法是所有排序方法中最簡單的一種演算法,其主要的實現思想是將資料按照一定的順序一個一個的插入到有序的表中,最終得到的序列就是已經排序好的資料。 直接插入排序是插入排序演算法中的一種,採用的方法是:在新增新的記錄時,使用順序查詢的方式找到其要插入的位置,然後將新記錄插入。 很多初學者所說的插入排

順序表的基本操作C語言實現(詳解版)

我們學習了順序表及初始化的過程,本節學習有關順序表的一些基本操作,以及如何使用 C 語言實現它們。 順序表插入元素 向已有順序表中插入資料元素,根據插入位置的不同,可分為以下 3 種情況: 插入到順序表的表頭; 在表的中間位置插入元素; 尾隨順序表中已有元素,作為順序表中的最後一個元素; 雖然資

CRC16常見幾個標準的演算法C語言實現

CRC16常見的標準有以下幾種,被用在各個規範中,其演算法原理基本一致,就是在資料的輸入和輸出有所差異,下邊把這些標準的差異列出,並給出C語言的演算法實現。CRC16_CCITT:多項式x16+x12+x5+1(0x1021),初始值0x0000,低位在前,高位在後,結果與0

JVM記憶體配置引數、GC工作原理Minor GC、FullGC

對於JVM記憶體配置引數:-Xmx10240m -Xms10240m -Xmn5120m -XXSurvivorRatio=3,其最小記憶體值和Survivor區總大小分別是()5120m,1024m5120m,2048m10240m,1024m10240m,2048mD-X

編譯原理(七) 算符優先分析法(構造算符優先關係表演算法C++實現)

概念簡述 移動歸約分析法:自底向上的語法分析方法,也稱為移動歸約分析法。 最易於實現的一種移動歸約分析方法,叫做算符優先分析法, 而更一般的移動歸約分析方法叫做LR分析法,LR分析法可以用作許多自動的語法分析器的生成器。 短語:文法G[S],αβδ是文

順序佇列的基本演算法迴圈佇列

佇列的順序儲存結構型別描述如下: #define M 1000 QElemType queue[ M ]; int front, rear; 隊頭指標front指出實際隊頭元素所在位置的前一個位置,而隊尾指標rear指出實際隊尾元素所在的位置,初始時,佇列為空有front

跳躍表(skip list)基本原理C/C++實現

1、基本原理不想好好寫字走的freestyle......其實可以直接看程式碼,我的註釋還是很詳細的,後面也有說明性的插圖。後面關於時間複雜度的分析證明沒有貼(總之我們都知道它效能棒棒噠就行了)。2、C/C++實現比較符合MIT公開課的實現是這篇博文:跳躍表實現而其他多數人都

GC回收演算法&&GC回收器

GC回收演算法 什麼是垃圾? 類比日常生活中,如果一個東西經常沒被使用,那麼就可以說是垃圾。 同理,如果一個物件不可能再被引用,那麼這個物件就是垃圾,應該被回收。 垃圾:不可能再被引用的物件。 finalize方法 在物件沒有被引用時呼叫 在Object類裡定義 新生代與老年代 IBM公司的研究表明,

HotSpot的演算法實現 垃圾回收機制GC

上一篇文章垃圾回收機制(GC)從理論上介紹了物件存活判定演算法和垃圾收集演算法,而在HotSpot虛擬機器上實現這些演算法時,必須對演算法的執行效率有嚴格的考量,才能保證虛擬機器高效執行。 列舉根節點   以可達性分析中從GC Roots 節點找引用鏈這個操作為例,可作為GC 

資料結構c語言版 嚴蔚敏 順序線性表12個基本操作演算法的實現

標頭檔案: c1.h (相關標頭檔案及函式結果狀態程式碼集合) /* c1.h (程式名) */ #include<string.h> #include<ctype.h> #include<malloc.h> /

(5) Java GC演算法種類

GC演算法主要有以下三種方法(都是以GC Roots可達性為依據,引用計數演算法實現簡單,但由於存在迴圈引用問題,故已不採用,詳見:Java GC(概述)) (1).複製收集演算法 針對Youn

阿里面試官都愛問的記憶體管理和GC演算法回收策略

JVM記憶體組成結構 JVM棧由堆、棧、本地方法棧、方法區等部分組成,結構圖如下所示: JVM記憶體回收 Sun的JVMGene

.NET MVC 擴展html屬性問題C#基本問題

問題 是你 blog color 性問題 easy style 如何解決 希望 本人菜鳥一枚,以下是我在項目中遇到一些問題的解決方法。 初次接觸到.net mvc發現html的有些屬性無法實現,比如使用easyui的data-options屬性會發生以下錯誤: 遇到這