CoreCLR源码探索(三) GC内存分配器的内部实现

在前一篇中我讲解了new是怎么工作的, 但是却一笔跳过了内存分配相关的部分.
在这一篇中我将详细讲解GC内存分配器的内部实现.
在看这一篇之前请必须先看完微软BOTR文档中的"Garbage Collection Design",
原文地址是: https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
译文可以看知平软件的译文或我后来的译文
请务必先看完"Garbage Collection Design", 否则以下内容你很可能会无法理解

服务器GC和工作站GC

关于服务器GC和工作站GC的区别, 网上已经有很多资料讲解这篇就不再说明了.
我们来看服务器GC和工作站GC的代码是怎么区别开来的.
默认编译CoreCLR会对同一份代码以使用服务器GC还是工作站GC的区别编译两次, 分别在SVR和WKS命名空间中:

源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcsvr.cpp

#define SERVER_GC 1namespace SVR { 
#include "gcimpl.h"#include "gc.cpp"}

源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcwks.cpp

#ifdef SERVER_GC#undef SERVER_GC#endifnamespace WKS { 
#include "gcimpl.h"#include "gc.cpp"}

当定义了SERVER_GC时, MULTIPLE_HEAPS和会被同时定义.
定义了MULTIPLE_HEAPS会使用多个堆(Heap), 服务器GC每个cpu核心都会对应一个堆(默认), 工作站GC则全局使用同一个堆.

源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h

#ifdef SERVER_GC#define MULTIPLE_HEAPS 1#endif // SERVER_GC

后台GC无论是服务器GC还是工作站GC都会默认支持, 但运行时不一定会启用.

源代码: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h

#define BACKGROUND_GC //concurrent background GC (requires WRITE_WATCH)

我们从https://www.microsoft.com/net下回来的CoreCLR安装包中已经包含了服务器GC和后台GC的支持,但默认不会开启.
开启它们可以修改project.json中的·runtimeOptions·节, 例子如下:

{"runtimeOptions": {"configProperties": {"System.GC.Server": true,"System.GC.Concurrent": true}}}

设置后发布项目可以看到coreapp.runtimeconfig.json, 运行时会只看这个文件.
微软官方的文档: https://docs.microsoft.com/en-us/dotnet/articles/core/tools/project-json

GC相关的类和它们的关系

我先用两张图来解释服务器GC和工作站GC下GC相关的类的关系

图中一共有5个类型

  • GCHeap

    • 实现了IGCHeap接口, 公开GC层的接口给EE(运行引擎)层调用

    • 在工作站GC下只有一个实例, 不会关联gc_heap对象, 因为工作站GC下gc_heap的所有成员都会被定义为静态变量

    • 在服务器GC下有1+cpu核心数个实例(默认), 第一个实例用于当接口, 其它对应cpu核心的实例都会各关联一个gc_heap实例

  • gc_heap

    • 内部的使用的堆类型, 用于负责内存的分配和回收

    • 在工作站GC下无实例, 所有成员都会定义为静态变量

    • 在工作站GC下generation_table这个成员不会被定义, 而是使用全局变量generation_table

    • 在服务器GC下有cpu核心数个实例(默认), 各关联一个GCHeap实例

  • generation

    • 储存各个代的信息, 例如地址范围和使用的段

    • 储存在generation_table中, 一个generation_table包含了5个generation, 前面的是0 1 2 3代, 最后一个不会被初始化和使用

    • 在工作站GC下只有1个generation_table, 就是全局变量generation_table

    • 在服务器GC下generation_table是gc_heap的成员, 有多少个gc_heap就有多少个generation_table

  • heap_segment

    • 堆段, 供分配器使用的一段内存, 用链表形式保存

    • 每个gc_heap中都有一个或一个以上的segment

    • 每个gc_heap中都有一个ephemeral heap segment(用于存放最年轻对象)

    • 每个gc_heap中都有一个large heap segment(用于存放大对象)

    • 在工作站GC下segment的默认大小是256M(0x10000000字节)

    • 在服务器GC下segment的默认大小是4G(0x100000000字节)

  • alloc_context

    • 分配上下文, 指向segment中的一个范围, 用于实际分配对象

    • 每个线程都有自己的分配上下文, 因为指向的范围不一样所以只要当前范围还有足够空间, 分配对象时不需要线程锁

    • 分配上下文的默认范围是8K, 也叫分配单位(Allocation Quantum)

    • 分配小对象时会从这8K中分配, 分配大对象时则会直接从段(segment)中分配

    • 代0(gen 0)还有一个默认的分配上下文供内部使用, 和线程无关

GCHeap的源代码摘要:

GCHeap的定义: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h#L61
全局的GCHeap实例: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L105

这里是1.1.0的代码, 1.2.0全局GCHeap会分别保存到gcheaputilities.h(g_pGCHeap)和gc.cpp(g_theGCHeap), 两处地方都指向同一个实例.

// 相当于extern GCHeap* g_pGCHeap;GPTR_DECL(GCHeap, g_pGCHeap);

gc_heap的源代码摘要:

gc_heap的定义: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h#L1079
这个类有300多个成员(从ephemeral_low开始),

generation的源代码摘要:

generation的定义: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h#L754
这里我只列出这篇文章涉及到的成员

class generation
{public:    // 默认的分配上下文alloc_context   allocation_context;    // 用于分配的最新的堆段heap_segment*   allocation_segment;    // 开始的堆段PTR_heap_segment start_segment;    // 用于区分对象在哪个代的指针, 在此之后的对象都属于这个代, 或比这个代更年轻的代uint8_t*        allocation_start;    // 用于储存和分配自由对象(Free Object, 又名Unused Array, 可以理解为碎片空间)的分配器allocator       free_list_allocator;    // 这个代是第几代int gen_num;
};

heap_segment的源代码摘要:

heap_segment的定义: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h#L4166
这里我只列出这篇文章涉及到的成员

class heap_segment
{public:    // 已实际分配地址 (mem + 已分配大小)// 更新有可能会延迟uint8_t*        allocated;    // 已提交到物理内存的地址 (this + SEGMENT_INITIAL_COMMIT)uint8_t*        committed;    // 预留到的分配地址 (this + size)uint8_t*        reserved;    // 已使用地址 (mem + 已分配大小 - 对象头大小)uint8_t*        used;    // 初始分配地址 (服务器gc开启时: this + OS_PAGE_SIZE, 否则: this + sizeof(*this) + alignment)uint8_t*        mem;    // 下一个堆段PTR_heap_segment next;    // 属于的gc_heap实例gc_heap*        heap;
};

alloc_context的源代码摘要:

alloc_context的定义: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L162
这里是1.1.0的代码, 1.2.0这些成员移动到了gcinterface.h的gc_alloc_context, 但是成员还是一样的

struct alloc_context 
{    // 下一次分配对象的开始地址uint8_t*       alloc_ptr;    // 可以分配到的最终地址uint8_t*       alloc_limit;    // 历史分配的小对象大小合计int64_t        alloc_bytes; //Number of bytes allocated on SOH by this context// 历史分配的大对象大小合计int64_t        alloc_bytes_loh; //Number of bytes allocated on LOH by this context#if defined(FEATURE_SVR_GC)// 空间不够需要获取更多空间时使用的GCHeap// 分alloc_heap和home_heap的作用是平衡各个heap的使用量,这样并行回收时可以减少处理各个heap的时间差异SVR::GCHeap*   alloc_heap;    // 原来的GCHeapSVR::GCHeap*   home_heap;#endif // defined(FEATURE_SVR_GC)// 历史分配对象次数int            alloc_count;
};

堆段的物理结构

为了更好理解下面即将讲解的代码,请先看这两张图片

分配对象内存的代码流程

还记得上篇我提到过的AllocateObject函数吗? 这个函数由JIT_New调用, 负责分配一个普通的对象.
让我们来继续跟踪这个函数的内部吧:

AllocateObject函数的内容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931
AllocateObject的其他版本同样也会调用AllocAlign8或Alloc函数, 下面就不再贴出其他版本的函数代码了.

Alloc函数的内容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931

GetGCHeap函数的内容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L377

static GCHeap *GetGCHeap(){LIMITED_METHOD_CONTRACT;    // 返回全局的GCHeap实例// 注意这个实例只作为接口使用,不和具体的gc_heap实例关联_ASSERTE(g_pGCHeap != NULL);    return g_pGCHeap;
}

GetThreadAllocContext函数的内容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L54

inline alloc_context* GetThreadAllocContext(){WRAPPER_NO_CONTRACT;assert(GCHeap::UseAllocationContexts());   
 // 获取当前线程并返回m_alloc_context成员的地址return & GetThread()->m_alloc_context; }

GCHeap::Alloc函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp

分配小对象内存的代码流程

让我们来看一下小对象的内存是如何分配的

allocate函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数尝试从分配上下文分配内存, 失败时调用allocate_more_space为分配上下文指定新的空间
这里的前半部分的处理还有汇编版本, 可以看上一篇分析的JIT_TrialAllocSFastMP_InlineGetThread函数

allocate_more_space函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数会在有多个heap时调用balance_heaps平衡各个heap的使用量, 然后再调用try_allocate_more_space函数


try_allocate_more_space函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数会获取MSL锁, 检查是否有必要触发GC, 然后根据gen_number参数调用allocate_small或allocate_large函数

allocate_small函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
循环尝试进行各种回收内存的处理和调用soh_try_fit函数, soh_try_fit函数分配成功或手段已经用尽时跳出循环


soh_try_fit函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数会先尝试调用a_fit_free_list_p从自由对象列表中分配, 然后尝试调用a_fit_segment_end_p从堆段结尾分配


a_fit_free_list_p函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数会尝试从自由对象列表中找到足够大小的空间, 如果找到则把分配上下文指向这个空间

a_fit_segment_end_p函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数会尝试在堆段的结尾找到一块足够大小的空间, 如果找到则把分配上下文指向这个空间

adjust_limit_clr函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数会给分配上下文设置新的范围
不管是从自由列表还是堆段的结尾分配都会调用这个函数, 从自由列表分配时seg参数会是nullptr
调用完这个函数以后分配上下文就有足够的空间了, 回到gc_heap::allocate的retry就可以成功的分配到对象的内存

总结小对象内存的代码流程

  • allocate: 尝试从分配上下文分配内存, 失败时调用allocate_more_space为分配上下文指定新的空间

    • try_allocate_more_space: 检查是否有必要触发GC, 然后根据gen_number参数调用allocate_small或allocate_large函数

    • soh_try_fit: 先尝试调用a_fit_free_list_p从自由对象列表中分配, 然后尝试调用a_fit_segment_end_p从堆段结尾分配

    • adjust_limit_clr: 给分配上下文设置新的范围

    • adjust_limit_clr: 给分配上下文设置新的范围

    • a_fit_free_list_p: 尝试从自由对象列表中找到足够大小的空间, 如果找到则把分配上下文指向这个空间

    • a_fit_segment_end_p: 尝试在堆段的结尾找到一块足够大小的空间, 如果找到则把分配上下文指向这个空间

    • allocate_small: 循环尝试进行各种回收内存的处理和调用soh_try_fit函数

    • allocate_more_space: 调用try_allocate_more_space函数

分配大对象内存的代码流程

让我们来看一下大对象的内存是如何分配的
分配小对象我们从gc_heap::allocate开始跟踪, 这里我们从gc_heap::allocate_large_object开始跟踪

allocate_large_object函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数和allocate函数不同的是它不会尝试从分配上下文中分配, 而是直接从堆段中分配

allocate_more_space这个函数我们在之前已经看过了, 忘掉的可以向前翻
这个函数会调用try_allocate_more_space函数
try_allocate_more_space函数在分配大对象时会调用allocate_large函数

allocate_large函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数的结构和alloc_small相似但是内部处理的细节不一样

loh_try_fit函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
处理和soh_try_fit差不多, 先尝试调用a_fit_free_list_large_p从自由对象列表中分配, 然后尝试调用loh_a_fit_segment_end_p从堆段结尾分配


a_fit_free_list_large_p函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
和a_fit_free_list_p的处理基本相同, 但是在支持LOH压缩时会生成填充对象, 并且有可能会调用bgc_loh_alloc_clr函数

adjust_limit_clr这个函数我们在看小对象的代码流程时已经看过
这里看bgc_loh_alloc_clr函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数是在后台GC运行时分配大对象使用的, 需要照顾到运行中的后台GC


loh_a_fit_segment_end_p函数的内容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
这个函数会遍历第3代的堆段链表逐个调用a_fit_segment_end_p函数尝试分配

总结大对象内存的代码流程

  • allocate_large_object: 调用allocate_more_space为一个空的分配上下文指定新的空间, 空间大小会等于对象的大小

    • try_allocate_more_space: 检查是否有必要触发GC, 然后根据gen_number参数调用allocate_small或allocate_large函数

    • loh_try_fit: 先尝试调用a_fit_free_list_large_p从自由对象列表中分配, 然后尝试调用loh_a_fit_segment_end_p从堆段结尾分配

    • a_fit_segment_end_p: 尝试在堆段的结尾找到一块足够大小的空间, 如果找到则把分配上下文指向这个空间

    • bgc_loh_alloc_clr: 给分配上下文设置新的范围, 照顾到后台GC

    • adjust_limit_clr: 给分配上下文设置新的范围

    • bgc_loh_alloc_clr: 给分配上下文设置新的范围, 照顾到后台GC

    • adjust_limit_clr: 给分配上下文设置新的范围

    • a_fit_free_list_large_p: 尝试从自由对象列表中找到足够大小的空间, 如果找到则把分配上下文指向这个空间

    • loh_a_fit_segment_end_p: 遍历第3代的堆段链表逐个调用a_fit_segment_end_p函数尝试分配

    • allocate_large: 循环尝试进行各种回收内存的处理和调用soh_try_fit函数

    • allocate_more_space: 调用try_allocate_more_space函数

CoreCLR如何管理系统内存 (windows, linux)

看到这里我们应该知道分配上下文, 小对象, 大对象的内存都是来源于堆段, 那堆段的内存来源于哪里呢?
GC在程序启动时会创建默认的堆段, 调用流程是init_gc_heap => get_initial_segment => make_heap_segment
如果默认的堆段不够用会创建新的堆段
小对象的堆段会通过gc1 => plan_phase => soh_get_segment_to_expand => get_segment => make_heap_segment创建
大对象的堆段会通过allocate_large => loh_get_new_seg => get_large_segment => get_segment_for_loh => get_segment => make_heap_segment创建

默认的堆段会通过next_initial_memory分配内存, 这一块内存在程序启动时从reserve_initial_memory函数申请
reserve_initial_memory函数和make_heap_segment函数都会调用virtual_alloc函数

因为调用流程很长我这里就不一个个函数贴代码了, 有兴趣的可以自己去跟踪
virtual_alloc函数的调用流程是

virtual_alloc => GCToOSInterface::VirtualReserve => ClrVirtualAllocAligned => ClrVirtualAlloc =>
CExecutionEngine::ClrVirtualAlloc => EEVirtualAlloc => VirtualAlloc

如果是windows, VirtualAlloc就是同名的windows api
如果是linux或者macosx, 调用流程是VirtualAlloc => VIRTUALReserveMemory => ReserveVirtualMemory
ReserveVirtualMemory函数会调用mmap函数

ReserveVirtualMemory函数的内容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/pal/src/map/virtual.cpp#L894

CoreCLR在从系统申请内存时会使用VirtualAlloc或mmap模拟的VirtualAlloc
申请后会得到一块尚未完全提交到物理内存的虚拟内存(注意保护模式是PROT_NONE, 表示该块内存不能读写执行, 内核无需设置它的PageTable)
如果你有兴趣可以看一下CoreCLR的虚拟内存占用, 工作站GC启动时就占了1G多, 服务器GC启动时就占用了20G

之后CoreCLR会根据使用慢慢的把使用的部分提交到物理内存, 流程是

GCToOSInterface::VirtualCommit => ClrVirtualAlloc => CExecutionEngine::ClrVirtualAlloc =>
EEVirtualAlloc => VirtualAlloc

如果是windows, VirtualAlloc是同名的windowsapi, 地址会被显式指定且页保护模式为可读写(PAGE_READWRITE)
如果是linux或者macosx, VirtualAlloc会调用VIRTUALCommitMemory, 且内部会调用mprotect来设置该页为可读写(PROT_READ|PROT_WRITE)

当GC回收了垃圾对象, 不再需要部分内存时会把内存还给系统, 例如回收小对象后的流程是

gc1 => decommit_ephemeral_segment_pages => decommit_heap_segment_pages => GCToOSInterface::VirtualDecommit

GCToOSInterface::VirtualDecommit的调用流程是

GCToOSInterface::VirtualDecommit => ClrVirtualFree => CExecutionEngine::ClrVirtualFree =>
EEVirtualFree => VirtualFree

如果是windows, VirtualFree是同名的windowsapi, 表示该部分虚拟内存已经不再使用内核可以重置它们的PageTable
如果是linux或者macosx, VirtualFree通过mprotect模拟, 设置该页的保护模式为PROT_NONE

VirtualFree函数的内容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/pal/src/map/virtual.cpp#L1291

我们可以看出, CoreCLR管理系统内存的方式比较底层
在windows上使用了VirtualAlloc和VirtualFree
在linux上使用了mmap和mprotect
而不是使用传统的malloc和new
这样会带来更好的性能但同时增加了移植到其他平台的成本

动态调试GC分配对象内存的过程

要深入学习CoreCLR光看代码是很难做到的, 比如这次大部分来源的gc.cpp有接近37000行的代码, 如果直接看可以把一个像我这样的普通人看疯
为了很好的了解CoreCLR的工作原理这次我自己编译了CoreCLR并在本地用lldb进行了调试, 这里我分享一下编译和调试的过程
这里我使用了ubuntu 16.04 LTS, 因为linux上部署编译环境比windows要简单很多

下载CORECLR:

git clone https://github.com/dotnet/coreclr.git

切换到你正在使用的版本, 请务必切换不要直接去编译master分支

git checkout v1.1.0

参考微软的帮助安装好需要的包

# https://github.com/dotnet/coreclr/blob/master/Documentation/building/linux-instructions.mdecho "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.6 main" | sudo tee /etc/apt/sources.list.d/llvm.listwget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add -sudo apt-get update
sudo apt-get install cmake llvm-3.5 clang-3.5 lldb-3.6 lldb-3.6-dev libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev uuid-dev
cd coreclr
./build.sh

执行build.sh会从微软的网站下载一些东西, 如果很长时间都下载不成功你应该考虑挂点什么东西
编译过程需要几十分钟, 完成以后可以在coreclr/bin/Product/Linux.x64.Debug下看到编译结果

完成以后用dotnet创建一个新的可执行项目, 在project.json中添加runtimes节

{"runtimes": {"ubuntu.16.04-x64": {}}
}

Program.cs的代码可以随意写, 想测哪部分就写哪部分的代码,我这里写的是多线程分配内存然后释放的代码

写完以后编译并发布

dotnet restoredotnet publish

发布后bin/Debug/netcoreapp1.1/ubuntu16.04-x64/publish会多出最终发布的文件
把刚才CoreCLR编译出来的coreclr/bin/Product/Linux.x64.Debug下的所有文件复制到publish目录下, 并覆盖原有文件
微软官方的调试文档可见 https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md

使用lldb启动进程, 这里我项目名称是coreapp所以publish下的可执行文件名称也是coreapp

lldb-3.6 ./coreapp

启动进程后可以打命令来调试, 需要中断(暂停)程序运行可以按下ctrl+c

这张图中的命令

b allocate_small
给函数下断点, 这里的allocate_small虽然全名是SVR::gc_heap::allocate_small或WKS::
gc_heap::allocate_small 但是lldb允许用短名称下断点, 碰到多个符合的函数会一并截取r 运行程序, 之前在pending中的断点如果在程序运行后可以确定内存位置则实际的添加断点bt 查看当前的堆栈调用树, 可以看当前被调用的函数的来源是哪些函数


这张图中的命令

n
步过, 遇到函数不会进去, 如果需要步进可以用s
另外步过汇编和步进汇编是ni和sifr v
查看当前堆栈帧中的变量
也就是传入的参数和本地变量p acontext->alloc_ptr
p *acontext打印全局或本地变量的值, 这个命令是调试中必用的命令, 不仅支持查看变量还支持计算表达式


这张图中的命令

c继续中断进程直到退出或下一个断点br del
删除之前设置的所有断点


这张图显示的是线程列表中的第一个线程的分配上下文内容, 0x168可以通过p &((Thread*)nullptr)->m_Link计算得出(就是offsetof)
这张图中的命令

me re -s4 -fx -c12 0x00007fff5c006f00读取0x00007fff5c006f00开始的内存, 单位是4byte, 表现形式是hex, 显示12个单位


lldb不仅能调试CoreCLR自身的代码
还能用来调试用户写的程序代码, 需要微软的SOS插件支持
详细可以看微软的官方文档 https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md

最后附上在这次分析中我常用的lldb命令
学习lldb可以查看官方的Tutorial和GDB and LLDB command examples

参考链接

https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcsvr.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcwks.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L162
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931
https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/pal/src/map/virtual.cpp#L894
https://github.com/dotnet/coreclr/blob/master/Documentation/building/linux-instructions.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md
https://docs.microsoft.com/en-us/dotnet/articles/core/tools/project-json
https://github.com/dotnet/coreclr/issues/8959
https://github.com/dotnet/coreclr/issues/8995
https://github.com/dotnet/coreclr/issues/9053

因为gc的代码实在庞大并且注释少, 这次的分析我不仅在官方的github上提问了还动用到lldb才能做到初步的理解
下一篇我将讲解GC内存回收器的内部实现, 可能需要的时间更长, 请耐心等待吧

原文地址:http://www.cnblogs.com/zkweb/p/6379080.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/326960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue学习1

P1 01_Vue学习目标03:50 P2 02_前端知识体系16:27 P3 03_前后端分离的演变史17:13 P4 04_前端MVVM模式09:31 P5 05_Vue是什么07:23 P6 06_第一个Vue应用程序07:06 P7 07_Vue实例声明周期05:35 P8 08_条件渲染06:59 P9 09_列表渲染03:34 P10 10_事件处理03:44…

Tomcat 的 Server 文件配置详解

转载自 Tomcat 的 Server 文件配置详解 前言 Tomcat隶属于Apache基金会,是开源的轻量级Web应用服务器,使用非常广泛。server.xml是Tomcat中最重要的配置文件,server.xml的每一个元素都对应了Tomcat中的一个组件;通过对xml文件中…

.Net基础体系和跨框架开发普及

.net体系经过十几年发展,发生了很多变化。特别是在最近两年,随着开源和跨平台的发展,衍生出很多概念,像标准库,可移植库,.Net Core等,相信有不少同学对他们之间的关系是有一些困惑的&#xff0c…

‘1‘ VS 1

‘1’-481; 把字符转换为数字,利用ASCALL表

一次恐怖的 Java 内存泄漏排查实战

转载自 一次恐怖的 Java 内存泄漏排查实战 最近在看《深入理解Java虚拟机:JVM高级特性与最佳实践》(第二版)这本书,理论实践结合,深入浅出,强烈推荐给大家。 这两天对JVM内容进行了一个讨论,…

ASP.NET与ASP.NET Core用户验证Cookie并存解决方案

在你将现有的用户登录(Sign In)站点从ASP.NET迁移至ASP.NET Core时,你将面临这样一个问题——如何让ASP.NET与ASP.NET Core用户验证Cookie并存,让ASP.NET应用与ASP.NET Core应用分别使用各自的Cookie?因为ASP.NET用的是…

vue学习2

P1 01_Vue学习目标03:50 P2 02_前端知识体系16:27 P3 03_前后端分离的演变史17:13 P4 04_前端MVVM模式09:31 P5 05_Vue是什么07:23 P6 06_第一个Vue应用程序07:06 P7 07_Vue实例声明周期05:35 P8 08_条件渲染06:59 P9 09_列表渲染03:34 P10 10_事件处理03:44…

一道非常棘手的 Java 面试题:i++ 是线程安全的吗

转载自 一道非常棘手的 Java 面试题:i 是线程安全的吗 i 是线程安全的吗? 相信很多中高级的 Java 面试者都遇到过这个问题,很多对这个不是很清楚的肯定是一脸蒙逼。内心肯定还在质疑,i 居然还有线程安全问题?只能说…

Microsoft规划了.NET的未来发展

Microsoft的Mads Torgersen分享了.NET语言家族的更新策略,给出了对公司未来的功能考虑的深刻理解。虽然C#、VB.NET和F#的开发是通过GitHub公开进行的,但是Microsoft的长远规划却经常是保密的。公众如果对Microsoft目前思考问题的方式有相关的意见和建议的…

逆波兰计算器实现

逆波兰计算器 思路分析 代码实现 package com.atguigu.stack;import java.security.AlgorithmConstraints; import java.util.ArrayList; import java.util.List; import java.util.Stack;/*** 创建人 wdl* 创建时间 2021/3/20* 描述*/ public class PolandNotation {public …

逆波兰表达式中缀表达式转换为后缀表达式

中缀表达式转换为后缀表达式 思路分析 代码实现 package com.atguigu.stack;import javax.swing.plaf.nimbus.State; import java.security.AlgorithmConstraints; import java.util.ArrayList; import java.util.List; import java.util.Stack;/*** 创建人 wdl* 创建时间 20…

OSS.Common扩展.Net Standard支持实例分享

上篇(.Net基础体系和跨框架开发普及)介绍了.Net当前生态下的大概情况,也分享了简单实现的过程,这篇文章就是讲解我的OSS.Common项目扩展.Net Standard 支持的过程,主要集中在:方案的选择,移植检…

A configuration error occurred during startup.Please verify the preference field with the prompt: To

今天遇到了一个棘手的问题,到现在都没有解决,折腾了一天结果捣鼓出来个更棘手的问题,经过多方继续折腾,终于把后来的这个问题给搞定了,但是前面的问题还是没有解决。有遇到相类似的问题解决了的麻烦分享一下&#xff0…

150. 逆波兰表达式求值---JAVA---LeetCode

class Solution {public int evalRPN(String[] tokens) {//创建一个栈&#xff0c;只需要一个栈即可Stack<Integer> stack new Stack<>();//遍历 lsfor(String item:tokens){//这里使用正则表达式来取出数if(isNumber(item)){//匹配的是多位数//入栈stack.push(In…

look look C#7

vs2017也rc好几个版本了&#xff0c;本想跟进看看c#7加入了什么内容&#xff0c;去搜索c#7&#xff0c;确实找到了不少文章&#xff0c;无奈很多特性ide根本不让编译啊。。。所以今天主要列出已经确定了的c#7特性&#xff08;一般来说rc后也不会加入新的特性了&#xff0c;其它…

jQuery Raty星级评分插件使用方法

转载自 jQuery Raty星级评分插件使用方法 使用jQuery Raty&#xff0c;可以很方便的在页面上嵌入一个评分组件&#xff0c;如下所示&#xff1a; 使用方法很简单&#xff0c;首先从https://github.com/wbotelhos/raty下载raty的源代码&#xff08;依赖于jquery&#xff09; …

迷宫问题---递归解决

题目描述 代码实现 package com.atguigu.recursion;/*** 创建人 wdl* 创建时间 2021/3/21* 描述*/ public class MiGong {public static void main(String[] args) {//先创建一个二维数组&#xff0c;模拟迷宫//地图int[][] map new int[8][7];//使用1表示墙//上下全部置为1f…

20周年献礼:Visual Studio 2017正式版3月7日发布

微软透露 Visual Studio 2017 的开发工作已经接近尾声&#xff0c;即将进入 RTM 阶段。现在&#xff0c;微软正式宣布&#xff0c;Visual Studio 2017 正式版将于 3 月 7 日正式发布&#xff01;值得一提的是&#xff0c;今年正好是 Visual Studio 诞生 20 周年纪念。 为了表示…