您的位置:首页 > 运维架构 > 网站架构

CLR探索系列:GC Heap架构及其实现(垃圾回收系列)

2008-02-20 10:51 603 查看
在CLR探索系列的研究DotNet下的垃圾回收器这几个博文里,就先说说GC Heap结构吧,至于垃圾回收的详析算法实现,以后再写了。

在一个托管进程被创建以后,在托管进程的内存空间里面,包含了System Domain,Shared Domain,Default Domain,以及一系列的Heap,有Process的Heap,JIT Code Heap,GC Heap以及LOH。

在DotNet的CLR的实现中,GC heap和LOH(Large Object Heap)是包含在一个类里面实现的。这就造成了在内存中,一个GC Heap后面紧跟着的就是LOH Heap。在实现的时候,一个gc_heap类里面包含了一个GC的结构类,以及一个LOH。

一个GC Heap的结构大致如下:



上面是一个GC Heap的大致结构。从上面到下面是从低地址到高地址。有的时候,需要找一个需要的存在了很久的Object的时候,并不是往高地址去寻找,也有可能存在于低地址区域,这个和每个Generation的大小的动态改变有关。

说到这里,就会提到一个非常有趣的概念,“策略”。这个是GC开发小组的开发人员在研究呢很久性能和用户的操作以及内存管理之间的平衡只会整出来的一个比较有趣的概念。在垃圾回收的过程中加入了“策略”。譬如一个Generation 0代占有256KB的内存大小,如果在实际的程序的运行过程中发现新对象的创建特别频繁,那么GC EE就会自动增加这个Generation 0的大小。

在说明一个GC Heap的结构的时候,先用windbg+sos来看看一个用户态运行的程序的GC Heap里面都有些啥:

0:003> !eeheap

=======================================

Number of GC Heaps: 1

generation 0 starts at 0x00ca1018

generation 1 starts at 0x00ca100c

generation 2 starts at 0x00ca1000

ephemeral segment allocation context: none

segment begin allocated size

00161e00 790d5588 790f4b38 0x0001f5b0(128432)

00ca0000 00ca1000 00ca3ff4 0x00002ff4(12276)

Large object heap starts at 0x01ca1000

segment begin allocated size

01ca0000 01ca1000 01ca3250 0x00002250(8784)

Total Size 0x247f4(149492)

------------------------------

GC Heap Size 0x247f4(149492)

=======================================

可以看到,在上面显示的GC Heap的结构中,一个GC Heap里面有两个Heap Segment,紧接着Heap Segment的,是LOH。

在上面的一个GC Heap中,分成三个Generation,最年轻的Object在G 0中。这些Generation是保存在一个一个的Heap Segment里面的。一个Heap Segment可以包含两个Generation或者三个,或者更多。而一个Generation可以跨越多个Heap Segment。

一个GC Heap主要有一个或者是多个Heap Segment组成。然后在GC Heap的最后还有一个保留区域,用来保存一些比较特别的Object。

譬如有一种类型的Object叫做pinned Object。当某个Object存在于Generation 0的内存区域中的时候,如果有其它的外部指针指向了它,再进行一次局部的垃圾回收的时候,这个Object就是不能被移走的,这样的对象就叫做pinned Object。但是,为了保证垃圾回收器的效率和性能,最年轻的Generation在每次垃圾回收的时候就会被refresh一次,清空所有的对象。先前存在的对象要么被移动到更老的Generation中去,而象这种pinned Object,就会被移动到GC Heap保留的内存区域里面去。

一个Heap Segment中可以包含一个或者多个Generation,这个是可以动态扩展的。

紧接着GC Heap的内存区域,就是Large Object Heap。在默认情况下,超过85000byte的对象就被保存到这里。

因为在LOH里面的对象清理一次对系统资源的消耗比较大,在存储大对象的时候,就会首先根据一个Object的MetaTable,扫描一遍来判断这个对象有没有对别的对象的引用。如果有的话,这一类对象就会存储在一起,而没有对其它对象引用的对象就会存在其余的另外一个区域。

这样做的好处,主要体现在性能的优化上面。在进行垃圾清理的时候,确定一个Object是否是live Object,LOH的这种布局方式可以减少很多不必要的对一个对象的扫描工作。

接下来,看看这些结构都是如何实现的,首先看看基本类gc_heap里面都有些什么:

//class definition of the internal class

class gc_heap

{

//GC Heap

friend class GCHeap;

friend class CFinalize;

friend void ProfScanRootsHelper(Object*&, ScanContext*, DWORD);

friend void GCProfileWalkHeap();

public:

//创建一个Heap Segment

heap_segment* make_heap_segment (BYTE* new_pages, size_t size);

//创建一个大对象堆,包含引用的对象

l_heap* make_large_heap (BYTE* new_pages, size_t size, BOOL managed);

gc_heap* make_gc_heap();

void destroy_gc_heap(gc_heap* heap);

static

HRESULT initialize_gc (size_t vm_block_size,

size_t segment_size,

size_t heap_size);

static

void shutdown_gc();

//allocate object in heap segments.这个分配动作并不分配大对象,只是分配在GC Heap上面的对象,而不是LOH上面的。LOH上面的对象分配是下面的allocate_large_object方法。

static

CObjectHeader* allocate (size_t jsize,

alloc_context* acontext );

CObjectHeader* try_fast_alloc (size_t jsize);

//allocate large object method,区别上面的allocate方法。

static

CObjectHeader* allocate_large_object (size_t size, BOOL pointerp, alloc_context* acontext);

//部分收集,对第几代进行垃圾收集。

static

int garbage_collect (int n);

//update brick table and card table。

static

int grow_brick_card_tables (BYTE* start, BYTE* end);

protected:

//获取一个Heap segment

static

heap_segment* get_segment ();

//得到lHeap,属于保存large objects with pointers。

static

l_heap* get_heap();

static

BOOL allocate_more_space (alloc_context* acontext, size_t jsize);

void reset_allocation_pointers (generation* gen, BYTE* start);

void delete_heap_segment (heap_segment* seg);

void delete_large_heap (l_heap* hp);

void adjust_ephemeral_limits ();

void make_generation (generation& gen, heap_segment* seg,

BYTE* start, BYTE* pointer);

int heap_grow_hook (BYTE* mem, size_t memsize, ptrdiff_t delta);

int heap_pregrow_hook (size_t memsize);

//对card table的操作。

size_t card_of ( BYTE* object);

BYTE* brick_address (size_t brick);

size_t brick_of (BYTE* add);

BYTE* card_address (size_t card);

size_t card_to_brick (size_t card);

void clear_card (size_t card);

void set_card (size_t card);

BOOL card_set_p (size_t card);

int grow_heap_segment (heap_segment* seg, size_t size);

void copy_brick_card_range (BYTE* la, BYTE* old_card_table,

short* old_brick_table,

heap_segment* seg,

BYTE* start, BYTE* end, BOOL heap_expand);

void copy_brick_card_table_l_heap ();

void copy_brick_card_table(BOOL heap_expand);

void clear_brick_table (BYTE* from, BYTE* end);

void set_brick (size_t index, ptrdiff_t val);

//根据是否是调试模式来确定是否进行引用的验证。

static

gc_heap* heap_of (BYTE* object, BOOL verify_p =

#ifdef _DEBUG

TRUE

#else

FALSE

#endif //_DEBUG

){return (gc_heap*)0;}

//下面的这些方法是在进行垃圾回收整理移动和复制对象的时候适用的。包括一系列的针对object还有card table以及进程的make,copy,fix和verify等动作。

BYTE* mark_object(BYTE* o );

BYTE* mark_object_class (BYTE* o );

void copy_object_simple_const (BYTE** o );

void get_copied_object (BYTE** o );

void scavenge_object_simple (BYTE* o );

//pinned object被移动到保留的Heap segment区域里面去。

void scavenge_pinned_object (BYTE* o );

void mark_object_internal (BYTE* o );

BYTE* next_end (heap_segment* seg, BYTE* f);

void fix_card_table ();

void verify_card_table ();

void mark_through_object (BYTE* oo );

BOOL process_mark_overflow (int condemned_gen_number);

void mark_phase (int condemned_gen_number, BOOL mark_only_p);

//把一个object整成pinned object

void pin_object (BYTE* o, BYTE* low, BYTE* high);

//update brick table

size_t update_brick_table (BYTE* tree, size_t current_brick,

BYTE* x, BYTE* plug_end);

//对card和bricktable的操作

void clear_cards (size_t start_card, size_t end_card);

void clear_card_for_addresses (BYTE* start_address, BYTE* end_address);

void copy_cards (size_t dst_card, size_t src_card,

size_t end_card, BOOL nextp);

void copy_cards_for_addresses (BYTE* dest, BYTE* src, size_t len);

BOOL ephemeral_pointer_p (BYTE* o);

void fix_brick_to_highest (BYTE* o, BYTE* next_o);

void copy_through_cards_helper (BYTE** poo, unsigned int& ngen, card_fn fn);

void copy_through_cards_for_segments (card_fn fn);

/* ------------------- per heap members --------------------------*/

public:

BYTE* card_table;

short* brick_table;

BYTE* lowest_address;

BYTE* highest_address;

protected:

heap_segment* ephemeral_heap_segment;

//定义一个GC可以有几个Generation

int condemned_generation_num;

BYTE* scavenge_list;

BYTE* last_scavenge;

BOOL pinning;

BYTE* gc_low; // lowest address being condemned

BYTE* gc_high; //highest address being condemned

size_t segment_size; //size of each heap segment

size_t lheap_size; //size of each lheap

BYTE* min_overflow_address;

BYTE* max_overflow_address;

GCSpinLock more_space_lock; //lock while allocating more space

GCSpinLock gc_lock; //lock while allocating more space

dynamic_data dynamic_data_table [NUMBERGENERATIONS+1];

//Large object support

l_heap* lheap;

BYTE* lheap_card_table;

gmallocHeap* gheap;

large_object_block* large_p_objects;

large_object_block** last_large_p_object;

large_object_block* large_np_objects;

//定义多大的对象就会保存到LOH中去。这个默认定义的大小是85000

size_t large_objects_size;

size_t large_blocks_size;

BOOL gen0_bricks_cleared;

CFinalize* finalize_queue;

/* ----------------------- global members ----------------------- */

public:

//定义最大支持几代的垃圾回收

static

int g_max_generation;

//定义一个保留的内存区域

static

size_t reserved_memory;

static

size_t reserved_memory_limit;

}; // class gc_heap

在上面的gc _heap类中,将对传统意义上面的GC Heap的管理和对LOH的管理,整合到了一起。这两部分对于垃圾回收是关系密切不可分离的两个部分,虽然LOH并不会有类似于带的概念,也不进行compaction的操作,但是个人更加愿意把LOH归类于垃圾回收的一个子系统。

上面说到,LOH和GC Heap一个很大的不同,就是LOH并不进行Heap的压缩工作。Heap的压缩是说,譬如在Generation 1中,经常进行Object的分配和finalize,就会造成这个区域的很多碎片,这个对于整个Heap的效率内存空间都有比较大的负面影响。关于对Heap的Compaction压缩,这是一个非常有趣好玩的事情,当然不是一两句能够说清楚的,这篇文章的重点在于说说GC Heap的结构,这个有趣的解决方法就在以后的博文里面探讨把。

这里只说说LOH的Heap为什么没有Compaction。因为在一个LOH中移动一个大对象是非常消耗系统资源的。LOH在实现的时候,是采用了开源的malloc-style heap的实现。这个实现中并没有涉及到Heap的压缩。基于性能的考虑,MS采用了一种折中的解决方案。给这个LOH中划分出了两种不同类型的Heap,也就是上面提到的,一种存放不包含pointer的Heap,另外一种是包含pointer的Heap。对对象的这种判断,是基于其Metatable的。

接下来,看看Heap Segment的实现:

class heap_segment

{

public:

//指向已经分配了的内存的末端

BYTE* allocated;

BYTE* committed;

//指向一个segment的保留区域

BYTE* reserved;

BYTE* used;

BYTE* mem;

//对所有的Heap segment都是链在一起成一条链的。

heap_segment* next;

BYTE* plan_allocated;

BYTE* padx;

BYTE* pad0;

#if ((SIZEOF_OBJHEADER+ALIGNFIXUP) % 8) != 0

BYTE pad1[8 - ((SIZEOF_OBJHEADER+ALIGNFIXUP) % 8)]; // Must pad to quad word

#endif

plug mPlug;

};

然后看看一个Heap segment里面的Generation是如何实现的:

class generation

{

public:

//分配上下文

alloc_context allocation_context;

//这个generation存在的Heap segment

heap_segment* allocation_segment;

BYTE* free_list;

heap_segment* start_segment;

BYTE* allocation_start;

BYTE* plan_allocation_start;

BYTE* last_gap;

//一个Generation里面剩余的空闲未分配空间

size_t free_list_space;

//generation里面的已经分配的空间的大小

size_t allocation_size;

};

还有一个问题提一下,就是在最上面的gc_heap的实现的时候,一个GC Heap里面的Heap Segment可以方便的添加和扩展,而且,在逻辑结构上面,同一个Generation的东西,可以分布在一个或者多个Heap segment里面。同时,gc_heap类的设计,也保证了可以方便的扩展一个GC Heap里面的Generation的个数。

对一个GC Heap的架构设计及其实现,就写到这里吧,虽然有许多细节问题没有写出来,敲的太多了,但是主要的问题也算讲清楚了吧 ^_^

在下面的文章中,就探索下垃圾回收的算法和回收实现机制,以及在垃圾回收里面的比较棘手的问题的挺有趣的解决方案吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: