国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

編寫你的第一個垃圾收集器

開發 前端
每當我倍感壓力以及有很多事情要做的時候,我總是有這樣一種反常的反應,那就是希望做一些其他的事情來擺脫這種狀況。通常情況下,這些事情都是些我能夠編寫并實現的獨立的小程序。

每當我倍感壓力以及有很多事情要做的時候,我總是有這樣一種反常的反應,那就是希望做一些其他的事情來擺脫這種狀況。通常情況下,這些事情都是些我能夠編寫并實現的獨立的小程序。

一天早上,我幾乎要被一堆事情給整瘋了——我得看一本書、處理一些工作上的事情、還要準備一場Strange Loop的演講,然后這時我突然想到:“我該寫一個垃圾收集器了”。

是的,我知道那一刻讓我看上去有多瘋狂。不過我的神經故障卻是你實現一段基礎的程序語言設計的免費教程!在100行左右毫無新意的c代碼中,我設法實現一個基本的標記和掃描模塊。

垃圾收集被認為是有更多編程牛人出沒的水域之一,但在這里,我會給你一個漂亮的兒童游泳池去玩耍。可能這里面仍然會有一些能手,但至少這會是一個淺水區。

 精簡、復用、再復用

垃圾收集背后有這樣一個基本的觀念:編程語言(大多數的)似乎總能訪問無限的內存。而開發者可以一直分配、分配再分配——像魔法一樣,取之不盡用之不竭。

當然,我們從來都沒有無限的內存。所以計算機實現收集的方式就是當機器需要分配一些內存,而內存又不足時,讓它收集垃圾

“垃圾(Garbage)”在這里表示那些事先分配過但后來不再被使用的內存。而基于對無限內存的幻想,我們需要確保“不再被使用”對于編程語言來說是非常安全的。要知道在你的程序試圖訪問一些隨機的對象時它們卻剛好正在得到回收,這可不是一件好玩的事情。

為了實現收集,編程語言需要確保程序不再使用那個對象。如果該程序不能得到一個對象的引用,那么顯然它也不會再去使用它。所以關于”in use”的定義事實上非常簡單:

  1. 任何被一個變量引用的對象,仍然在作用域內,就屬于”in use”狀態。
  2. 任何被另一個對象引用的對象,仍在使用中,就是”in use”狀態。

如果對象A被一個變量引用,而它又有一些地方引用了對象B,那么B就是在使用中(“in use”),因為你能夠通過A來訪問到它。

這樣到最后的結果就是得到一張可訪問的對象圖——以一個變量為起點并能夠遍歷到的所有對象。任何不在圖中的對象對于程序來說都是死的,而它的內存也是時候被回收了。

 標記并清理

有很多不同的方法可以實現關于查找和回收所有未被使用的對象的操作,但是最簡單也是第一個被提出的算法就是”標記-清除”算法。它由John McCarthy——Lisp(列表處理語言)的發明者提出,所以你現在做的事情就像是與一個古老的神在交流,但希望你別用一些洛夫克拉夫特式的方法——最后以你的大腦和視網膜的完全枯萎而結束。

該算法的工作原理幾乎與我們對”可訪問性(reachability)”的定義完全一樣:

  1. 從根節點開始,依次遍歷整個對象圖。每當你訪問到一個對象,在上面設置一個”標記(mark)”位,置為true。
  2. 一旦搞定,找出所有標記位為”not”的對象集,然后刪除它們。

對,就是這樣。我猜你可能已經想到了,對吧?如果是,那你可能就成為了一位被引用了數百次的文章的作者。所以這件事情的教訓就是,想要在CS(計算機科學)領域中出名,你不必開始就搞出一個很牛的東西,你只需要第一個整出來即可,哪怕這玩意看上去很搓。

對象對

在我們落實這兩個步驟之前,讓我們先做些不相關的準備工作。我們不會為一種語言真正實現一個解釋器——沒有分析器,字節碼、或任何這種愚蠢的東西。但我們確實需要一些少量的代碼來創建一些垃圾去收集。

讓我們假裝我們正在為一種簡單的語言編寫一個解釋器。它是動態類型,并且有兩種類型的變量:int 和 pair。 下面是用枚舉來標示一個對象的類型:

  1. typedef enum { 
  2.   OBJ_INT, 
  3.   OBJ_PAIR 
  4. } ObjectType; 

其中,pair可以是任何一對東西,兩個int、一個int和另一個pair,什么都可以。隨你怎么想都行。因為一個對象在虛擬機中可以是這兩個當中的任意一種類型,所以在c中實現對象的典型方法是時用一個標記聯合體(tagged union)。

  1. typedef struct sObject { 
  2.   ObjectType type; 
  3.   
  4.   union { 
  5.     <span style="color: #999999;">/* OBJ_INT */</span> 
  6.     int value; 
  7.   
  8.    <span style="color: #999999;"> /* OBJ_PAIR */</span> 
  9.     struct { 
  10.       struct sObject* head; 
  11.       struct sObject* tail; 
  12.     }; 
  13.   }; 
  14. } Object; 

這個Object結構擁有一個type字段表示它是哪種類型的值——要么是int要么是pair。接下來用一個union來持有這個int或是pair的數據。如果你對c語言很生疏,一個union就是一個結構體,它將字段重疊在內存中。由于一個給定的對象只能是int或是pair,我們沒有任何理在一個單獨的對象中同時為所有這3個字段分配內存。一個union就搞定。帥吧。

小虛擬機

現在我們可以將其包裝在一個小的虛擬機結構中了。它(指虛擬機)在這里的角色是用一個棧來存儲在當前作用域內的變量。大多數語言虛擬機要么是基于棧 (如JVM和CLR)的,要么是基于寄存器(如Lua)的。但是不管哪種情況,實際上仍然存在這樣一個棧。它用來存放在一個表達式中間需要用到的臨時變量 和局部變量。

我們來簡潔明了地建立這個模型,如下:

  1. #define STACK_MAX 256 
  2.   
  3. typedef struct { 
  4.   Object* stack[STACK_MAX]; 
  5.   int stackSize; 
  6. } VM; 

現在我們得到了一個合適的基本數據結構,接下來我們一起敲些代碼來創建些東西。首先,我們來寫一個方法創建并初始化一個虛擬機:

  1. VM* newVM() { 
  2.   VM* vm = malloc(sizeof(VM)); 
  3.   vm-&gt;stackSize = 0
  4.   return vm; 

一旦我們得到了虛擬機,我們需要能夠操作它的堆棧:

  1. void push(VM* vm, Object* value) { 
  2.   assert(vm-&gt;stackSize &lt; STACK_MAX, "Stack overflow!"); 
  3.   vm-&gt;stack[vm-&gt;stackSize++] = value; 
  4.   
  5. Object* pop(VM* vm) { 
  6.   assert(vm-&gt;stackSize &gt; 0, "Stack underflow!"); 
  7.   return vm-&gt;stack[--vm-&gt;stackSize]; 

好了,現在我們能敲些玩意到”變量”中了,我們需要能夠實際的創建對象。首先來一些輔助函數:

  1. Object* newObject(VM* vm, ObjectType type) { 
  2.   Object* object = malloc(sizeof(Object)); 
  3.   object-&gt;typetype = type; 
  4.   return object; 

它實現了內存的分配和設置類型標記。我們一會兒會重溫它的。利用它,我們可以編寫方法將每種類型的對象壓到虛擬機的棧上:

  1. void pushInt(VM* vm, int intValue) { 
  2.   Object* object = newObject(vm, OBJ_INT); 
  3.   object-&gt;value = intValue
  4.   push(vm, object); 
  5.   
  6. Object* pushPair(VM* vm) { 
  7.   Object* object = newObject(vm, OBJ_PAIR); 
  8.   object-&gt;tail = pop(vm); 
  9.   object-&gt;head = pop(vm); 
  10.   
  11.   push(vm, object); 
  12.   return object; 

這就是我們的小小虛擬機。如果我們有調用這些方法的解析器和解釋器,那我們手上就有了一種對上帝都誠實的語言。而且,如果我們有無限的內存,它甚至能夠運行真正的程序。可惜咱們沒有,所以讓我們來收集些垃圾吧。

標記

第一個階段就是標記(marking)。我們需要掃遍所有可以訪問到的對象,并設置其標志位。現在我們需要做的第一件事就是為對象添加一個標志位(mark bit):

  1. typedef struct sObject { 
  2.   unsigned char marked; 
  3.   <span style="color: #999999;">/* Previous stuff... */</span> 
  4. } Object; 

一旦我們創建了一個新的對象,我們將修改newObject()方法初始化marked為0。為了標記所有可訪問的對象,我們從內存中的變量入手,這樣就意味著要掃一遍堆棧。看上去就像這樣:

  1. void markAll(VM* vm) 
  2.   for (int i = 0; i &lt; vm-&gt;stackSize; i++) { 
  3.     mark(vm-&gt;stack[i]); 
  4.   } 

里面又調用了mark。我們來分幾步搭建它。第一:

  1. void mark(Object* object) { 
  2.   object-&gt;marked = 1

毫無疑問,這是最重要的一點。我們標記了這個對象自身是可訪問的,但記住,我們還需要處理對象中的引用:可訪問性是遞歸的。如果該對象是一個pair,它的兩個字段也是可訪問的。操作很簡單:

  1. void mark(Object* object) { 
  2.   object-&gt;marked = 1
  3.   
  4.   if (object-&gt;type == OBJ_PAIR) { 
  5.     mark(object-&gt;head); 
  6.     mark(object-&gt;tail); 
  7.   } 

但是這里有一個bug。你看到了嗎?我們正在遞歸,但我們沒有檢查循環。如果你有一堆pair在一個循環中相互指向對方,這就會造成棧溢出并崩潰。

為了解決這個情況,我們僅需要做的是在訪問到了一個已經處理過的對象時,退出即可。所以完整的mark()方法應該是:

  1. void mark(Object* object) { 
  2.   /* If already marked, we're done. Check this first 
  3.      to avoid recursing on cycles in the object graph. */ 
  4.   if (object->marked) return; 
  5.   
  6.   object->marked = 1
  7.   
  8.   if (object->type == OBJ_PAIR) { 
  9.     mark(object->head); 
  10.     mark(object->tail); 
  11.   } 

現在我們可以調用markAll()方法了,它會準確的標記內存中所有可訪問的對象。我們已經成功一半了!

清理

下一個階段就是清理一遍所有我們已經分配過(內存)的對象并釋放那些沒有被標記過的(對象)。但這里有一個問題:所有未被標記的對象——我們所定義的——都不可達!我們都不能訪問到它們!

虛擬機已經實現了對象引用的語義:所以我們只在變量和pair元素中儲存指向對象的指針。當一個對象不再被任何指針指向時,那我們就完全失去它了,而這也實際上造成了內存泄露。

解決這個問題的訣竅是:虛擬機可以有它自己的對象引用,而這不同于對語言使用者可讀的那種語義。換句話說,我們自己可以保留它們的痕跡。

這么做最簡單的方法是僅維持一張由所有分配過(內存)的對象(組成)的鏈表。我們在這個鏈表中將對象自身擴展為一個節點:

  1. typedef struct sObject { 
  2.   /* The next object in the list of all objects. */ 
  3.   struct sObject* next; 
  4.   
  5.   /* Previous stuff... */ 
  6. } Object; 

虛擬機會保留這個鏈表頭的痕跡:

  1. typedef struct { 
  2.   /* The first object in the list of all objects. */ 
  3.   Object* firstObject; 
  4.   
  5.   /* Previous stuff... */ 
  6. } VM; 

在newVM()方法中我們確保將firstObject初始化為NULL。無論何時創建一個對象,我們都將其添加到鏈表中:

  1. Object* newObject(VM* vm, ObjectType type) { 
  2.   Object* object = malloc(sizeof(Object)); 
  3.   object->typetype = type; 
  4.   object->marked = 0
  5.   
  6.   /* Insert it into the list of allocated objects. */ 
  7.   object->next = vm->firstObject; 
  8.   vm->firstObject = object
  9.   
  10.   return object; 

這樣一來,即便是語言找不到一個對像,它還是可以被實現。想要清理并刪除那些未被標記的對象,我們只需要遍歷該鏈表:

  1. void sweep(VM* vm) 
  2.   Object** object = &vm->firstObject; 
  3.   while (*object) { 
  4.     if (!(*object)->marked) { 
  5.       /* This object wasn't reached, so remove it from the list 
  6.          and free it. */ 
  7.       Object* unreached = *object; 
  8.   
  9.       *object = unreached->next; 
  10.       free(unreached); 
  11.     } else { 
  12.       /* This object was reached, so unmark it (for the next GC) 
  13.          and move on to the next. */ 
  14.       (*object)->marked = 0
  15.       object = &(*object)->next; 
  16.     } 
  17.   } 

這段代碼讀起來有點棘手,因為那個指針(指object)指向的是一個指針,但是通過它的工作你會發現它還是非常簡單的。它只是掃遍了整張鏈表。只要它碰到了一個未被標記的對象,它就會釋放該對象的內存并將其從鏈表中移除。最后,我們將會刪除所有不可訪問的對象。

祝賀你!我們已經有了一個垃圾收集器!現在只剩下一點工作了:實際調用它!首先我們將這兩個階段整合在一起:

  1. void gc(VM* vm) { 
  2.   markAll(vm); 
  3.   sweep(vm); 

沒有比這更明顯的”標記-清除”算法了。現在最棘手的是搞清楚什么時候來實際調用它。”內存不足(low on memory)”是個什么意思?尤其是對于現在的計算機,它們幾乎擁有無限的虛擬內存!

事實證明,我們沒有完全正確或錯誤的答案。這真的取決于你使用虛擬機的目的以及讓它運行在什么樣的硬件上。為了讓這個例子看上去很簡單,我們僅在進行了一定數量的內存分配之后開始收集。事實上一些語言的實現就是這么做的,而這也很容易。

我們將邀請虛擬機來追蹤我們到底創建了多少(對象):

  1. typedef struct { 
  2.   /* The total number of currently allocated objects. */ 
  3.   int numObjects; 
  4.   
  5.   /* The number of objects required to trigger a GC. */ 
  6.   int maxObjects; 
  7.   
  8.   /* Previous stuff... */ 
  9. } VM; 

接下來,初始化:

  1. VM* newVM() { 
  2.   /* Previous stuff... */ 
  3.   
  4.   vm->numObjects = 0
  5.   vm->maxObjects = INITIAL_GC_THRESHOLD
  6.   return vm; 

其中,INITIAL_GC_THRESHOLD為你啟動第一個GC(垃圾收集器)的對象數量。較小的值會更節省內存,而較大的值則更省時。自己看著辦吧。

每當我們創建一個對象,我們增加numObjects,如果它達到最大值就啟動一次收集:

  1. Object* newObject(VM* vm, ObjectType type) { 
  2.   if (vm->numObjects == vm->maxObjects) gc(vm); 
  3.   
  4.   /* Create object... */ 
  5.   
  6.   vm->numObjects++; 
  7.   return object; 

我不會費心的顯示它(指numObjects),但是我們也會稍微調整sweep()方法,每釋放一次就遞減numObjects。最后,我們修改了gc()方法來更新最大值:

  1. void gc(VM* vm) { 
  2.   int numObjects = vm->numObjects; 
  3.   
  4.   markAll(vm); 
  5.   sweep(vm); 
  6.   
  7.   vm->maxObjects = vm->numObjects * 2; 

每次收集之后,我們更新maxObjects——以進行收集后仍在活動的對象為基準。乘法器讓我們的堆隨著活動中的對象數量的增加而增加。同樣,也會隨著一些對象最終被釋放掉而自動減少。

最后

你成功了!如果你全部照做了,那你現在已經得到了一個簡單的垃圾收集算法的句柄。如果你想看完整的代碼,在這里。我再強調一點,盡管這個收集器很簡單,但它可不是一個玩具。

你可以在這上面做一大堆的優化(像在GC和程序設計語言這些事情中,90%的努力都在優化上),但它的核心代碼可是真正的GC。它與目前Ruby和Lua中的收集器非常的相似。你可以使用一些類似的代碼到你的項目中。去做些很酷的事情吧!

原文鏈接:http://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-collector/

譯文鏈接:http://blog.jobbole.com/53376/

責任編輯:陳四芳 來源: 伯樂在線
相關推薦

2014-07-24 14:35:26

Linux內核模塊

2019-12-31 08:00:00

DebianLinuxApple Swift

2024-08-26 08:58:50

2024-12-30 08:03:08

2011-07-21 14:54:26

java垃圾收集器

2021-04-07 13:38:27

Django項目視圖

2022-07-25 10:15:29

垃圾收集器Java虛擬機

2010-03-15 10:37:46

Pthon腳本

2022-10-17 10:28:05

Web 組件代碼

2021-12-30 11:26:31

語言編譯器腳本

2013-01-14 09:44:58

JavaScriptJSJS框架

2009-10-30 10:47:48

VB.NET垃圾收集器

2017-09-21 14:40:06

jvm算法收集器

2025-08-26 07:50:22

2023-11-16 08:00:56

Java11G1

2018-10-15 10:10:41

Linux內核補丁

2024-03-27 10:27:35

延遲垃圾收集器

2022-04-19 11:25:31

JVMZGC垃圾收集器

2025-07-11 02:33:00

JVM垃圾回收

2011-05-10 16:04:45

Java垃圾收集器
點贊
收藏

51CTO技術棧公眾號

欧美日韩在线免费观看视频| 国产精品蜜月aⅴ在线| 男男成人高潮片免费网站| 欧美激情中文字幕在线| 亚洲欧美韩国| 欧美v日韩v国产v| 国产高清视频在线播放| 亚洲国产欧美在线| 男人j桶女人的网站| 久久综合九色综合97婷婷| 欧美亚洲免费在线| 亚洲综合日本| 91麻豆桃色免费看| 国产videos久久| 国内揄拍国内精品少妇国语| 日本午夜精品久久久久| 国产午夜精品免费一区二区三区| 免费在线国产视频| 7777精品伊人久久久大香线蕉| 欧美视频综合| 在线精品亚洲一区二区不卡| 瑟瑟在线观看| 欧美中文字幕久久| h视频网站在线观看| 在线看国产日韩| 欧美日韩xx| 日韩一区二区在线观看| 在线āv视频| 亚洲国产精品大全| 在线人成日本视频| 中文字幕一精品亚洲无线一区 | 亚洲精品乱码久久久久久| 在线观看高清免费视频| 国产亚洲综合在线| 成年人在线观看视频免费| 国产精品灌醉下药二区| 色婷婷av金发美女在线播放| 亚洲国产中文字幕在线视频综合| 狠狠干在线视频| 在线免费亚洲电影| www.51av欧美视频| 精品国产一区二区三区久久久狼 | 老司机成人影院| www.久久久久| 欧美一级三级| 99r国产精品视频| 国产毛片久久| 黄色三级中文字幕| 中文字幕精品一区二区精品绿巨人| 蜜桃传媒av| 欧美优质美女网站| 成人影院在线播放| 另类天堂视频在线观看| 亚洲综合福利| 国产精品三区在线| 国产麻豆成人精品| 成人18网站| 欧美日韩国产系列| 国产69精品久久久久按摩| 欧美一级片免费在线| 亚洲三级免费| 黄色一级视频在线播放| 亚洲自拍与偷拍| 性欧美videos高清hd4k| 久久天天躁狠狠躁夜夜躁2014| 成人嘿咻视频免费看| 亚洲精品一区国产精品| 国产精品蜜臀在线观看| 天天在线视频色| 日韩在线观看免费高清| 日韩片欧美片| 一区二区三区四区视频在线| 国产精品网站在线观看| 2021国产在线| 海角国产乱辈乱精品视频| 午夜日本精品| 久久婷婷五月综合色国产香蕉| 色哟哟一区二区三区| 国产精品高清乱码在线观看| 国产精品福利在线观看网址| 日韩黄色一级片| 欧美精品第1页| 在线观看a级片| 91成人性视频| 日韩在线观看一区二区| av网站免费观看| 亚洲国产成人在线视频| 成人在线免费小视频| 亚洲国产高清国产精品| 亚洲综合色成人| 国产成人精品一区二区三区免费| 不卡一卡2卡3卡4卡精品在| 91免费版在线| 天天综合视频在线观看| 欧美一级视频在线观看| 九九国产精品视频| 蜜桃视频在线观看视频| 久久成人精品视频| 久久精品国产**网站演员| 最新天堂资源在线| 免费成人高清视频| 日韩vs国产vs欧美| 欧美偷拍视频| 久久久久久久91| 国产成人自拍高清视频在线免费播放| 国产乱子伦三级在线播放| 久久免费福利视频| 国产成人免费视频精品含羞草妖精 | 国产日韩亚洲精品| 亚洲人吸女人奶水| 日本免费成人| 超碰免费在线公开| 日韩午夜激情视频| 欧美区国产区| 色网址在线观看| 欧美中文字幕在线| 国产日韩综合av| 性欧美video另类hd尤物| 欧美一区二区三区在线免费观看 | 亚洲一区二区三区精品动漫| 欧美性猛片aaaaaaa做受| 激情婷婷综合| 91欧美视频在线| 色狠狠久久aa北条麻妃 | 成人毛片视频在线观看| 日韩精品亚洲人成在线观看| 97久久人人超碰caoprom欧美| 亚洲欧美激情在线| 动漫3d精品一区二区三区乱码| 日韩av新片网| 色妞欧美日韩在线| 不卡视频在线观看| 91麻豆精品国产91久久久更新资源速度超快| 视频一区国产精品| 日韩精品一区二区在线| 模特精品在线| 黄色片网站在线观看| 国产欧美日本在线| 欧美日韩亚洲综合一区 | jizz亚洲| 99se婷婷在线视频观看| 色哟哟欧美精品| 欧美激情五月| 春暖花开成人亚洲区| 国产尤物99| 日韩一区国产二区欧美三区| 日日夜夜一区二区| 中文字幕在线中文字幕在线中三区| 在线视频欧美一区| 亚洲欧美一区二区三区四区| proumb性欧美在线观看| 1204国产成人精品视频| 黑巨人与欧美精品一区| 国产精品一区二区三区成人| 欧美日韩午夜视频在线观看| 欧美区亚洲区| av资源在线| 欧美一区二区中文字幕| 国外成人性视频| 精品色蜜蜜精品视频在线观看| 亚洲国产激情| 中文字幕乱码在线播放| 日韩一级在线免费观看| 国产精品久久久久久久久久免费 | 午夜精品一区二区在线观看的 | 亚洲免费中文| 国产精品一区二区av影院萌芽| bt天堂新版中文在线地址| 精品自拍视频在线观看| 亚洲男人都懂的| 亚洲视频一区| 日本精品另类| 天天草夜夜草| 亚洲精品白虎| 韩国三级日本三级少妇99| 日韩欧美精品免费在线| 青青草91视频| heyzo欧美激情| 第一视频专区在线| 免费看毛片的网址| 国产精品一区二区久久精品| 日韩欧美123| 欧美经典三级视频一区二区三区| 欧美福利一区| av日韩久久| 日本在线播放| 日韩毛片在线免费看| 国产精品美女诱惑| 欧美理论电影在线播放| 欧美午夜精品一区二区三区| 91小视频免费观看| 日韩午夜在线| 欧美黑人巨大videos精品| 在线看三级电影| 成年人福利视频| 色撸撸在线观看| 国产日韩欧美视频| 色琪琪综合男人的天堂aⅴ视频| 日韩欧亚中文在线| 久久久综合精品|