垃圾回收之引用计数
source link: https://studygolang.com/articles/18123?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
—— 不管人非笑,不管人毁谤,不管人荣辱,任他功夫有进有退,我只是这致良知的主宰不息,久久自然有得力处
Reference Counting
对象在创建时保存一个自身被引用的计数,初始值为1。每次被新的变量引用,该值加1。相反,则减去1。当该值等于0时,占用空间被系统回收。
什么是对象呢?
var neojos int64 = 32 var ptrNeojos *int64 = &name
如上所示,我们创建了一个 int64
类型的 object
,命名为 neojos
。程序中对该 object
的操作都是通过使用 neojos
来实现的。而 ptrNeojos
其实又创建了一个 *int64
类型的 object
,但它的值保存的是 neojos
的地址。
对于 ptrNeojos
来说,它的生命周期跟普通变量的生命周期没有区别。唯一区别的是,当它生命周期结束后, ptrNeojos
会被垃圾回收,而底层指向的 object
却不会。
如何计数呢?
Object * obj1 = new Object(); // RefCount(obj1) starts at 1 Object * obj2 = obj1; // RefCount(obj1) incremented to 2 as new reference is added Object * obj3 = new Object(); obj2->SomeMethod(); obj2 = NULL; // RefCount(obj1) decremented to 1 as ref goes away obj1 = obj3; // RefCount(obj1) decremented to 0 and can be collected
obj1
指向了一个匿名对象,为了方便,我们叫 anonymousObj
。上述代码展示了 anonymousObj
从创建到被垃圾回收的整个过程。 垃圾回收对象的内存空间
,上述过程中 obj1
对象的地址不会发生改变,只是底层引用的对象发生了变化。
下面的例子,用于测试 ptrName
代表的对象在赋值过程中不会发生变化。
func TestCase2(t *testing.T) { var name int64 = 32 var ptrName *int64 = &name t.Log(&ptrName) //0xc42000e078 ptrName = nil t.Log(&ptrName) //0xc42000e078 }
如何实现
基于不同的语言会有不同的实现方式,但思路是相通的。
存储结构和申请空间
创建对象的时候,申请额外的空间用于存储引用计数,同时对外隐藏该空间的存在。如下图, Header
部分就用于存储引用计数。所以,程序返回的指针实际是 ActualData
的首地址,调用者完全意识不到 header
的存在,而 GC
执行的时候却可以通过对象的地址访问 Header
。
如下代码,申请地址时,将引用计数初始化为1。同时,返回 ActualData
的指针地址。后续的引用计数更新,释放对象空间都通过判断 Header
来处理。
//Header结构 struct MemHeader { UINT32 refCount; }; // cb is the number of bytes to be allocated PVOID GC_Alloc(size_t cb) { // allocate MemHeader + cb but cast it to MemHeader MemHeader* pHdr = (MemHeader*)PlatformAlloc(MEMHEADERSIZE + cb); if (pHdr == NULL) return NULL; // set the initial refCount pHdr->refCount = 1; // increment the pointer by the size of MemHeader // and make it point to the start of the actual data ++pHdr; return (PVOID)pHdr; } //访问Header头 inline MemHeader * GetHeader(PVOID pMem) { return ((MemHeader*)pMem) - 1; }
基类实现
对象可以意识到引用计数机制的存在,明确的增加或减少引用计数。这种情况适用于:调用者手动释放空间的场合。那么,所有对象需要继承一个通用的基类,来实现这部分计数逻辑。
class ReferenceCount { int count; ReferenceCount() { count = 1; //start at 1 as creation implies at least once reference is being made } void increment() { count++; } void decrement() { count--; if( count == 0 ) delete this; } }; //any reference counted object simply derives from the above type class MyType : public ReferenceCount { ... }
对象引用关系
对象与对象之间存在相互调用,当其中一个对象的引用计数减为0时,该对象“引用链”上其他对象的引用计数都需要被更新。 GC
如何执行清理的呢?
当 object1
释放 object3
的引用时, object3
和 object5
的引用计数都需要被更新,而这是一个递归检查、更新的过程。
VOID GC_ReleaseRef(PVOID pMem) { if (pMem == NULL) return; MemHeader *pHdr = GetHeader(pMem); --(pHdr->refCount); if (pHdr->refCount == 0) { foreach(PVOID pChild in Get_Child(pHdr)) GC_ReleaseRef(pChild); PlatformFree(pHdr); } }
GC扫描
除了自动回收垃圾外, GC
的扫描是从哪里开始的?拿 Java
来解释, GC roots
就是 Java
中的 ClassLoader
。
ClassLoader
After that when we try to use a Class, Java ClassLoader loads that class into memory
ClassLoader
按需将使用到的 class
加载到内存,熟悉 PHP
的可以跟 Laravel Container
做类比。
GC roots
In Java, there are special objects called Garbage Collection Roots (GC roots). They serve as a root objects for Garbage Collection marking mechanism (see picture).
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK