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,一经查实,立即删除!

相关文章

使用Servlet上传多张图片——Dao层(BaseDao.java)

package orz.treeSquirrels.dao;import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List;/*** * * 项目名称:test_uploadFile …

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…

使用Servlet上传多张图片——Dao层(ProductInfoDao.java)

package orz.treeSquirrels.dao;import orz.treeSquirrels.entity.ProductInfo;/*** 商品信息表的接口* author Administrator**/ public interface ProductInfoDao {//添加public int addProductInfo(ProductInfo pro);}对应的实现类(ProductInfoDaoImpl.java): pa…

Tomcat 的 Server 文件配置详解

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

charles抓包ios抓拍教程

charles抓包ios抓拍教程_百度搜索 https://www.jianshu.com/p/724ef9d3efb6 https://www.cnblogs.com/junhuawang/p/7280957.html https://jingyan.baidu.com/article/495ba841de143a38b20ede67.html https://www.cnblogs.com/gchlcc/p/7110902.html

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

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

使用Servlet上传多张图片——Service层(ProductInfoService.java和ProductInfoServiceImpl)

package orz.treeSquirrels.service;import orz.treeSquirrels.entity.ProductInfo;/*** 商品信息表的业务逻辑类接口* author Administrator**/ public interface ProductInfoService {/*** * Title: addProductInfo* Description: 添加商品信息* param param pro* param retu…

‘1‘ VS 1

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

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

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

肯德基app电脑端自动下单程序

肯德基app电脑端自动下单程序_百度搜索 定制款肯德基APP电脑端自动下单软件程序 - 软件开发 - 天盟网-国内领先的IT技术需求服务平台_创新型软件众包服务接单网_知识技能服务威客网 https://qz-m.oaqhsgl.cn/kfc/set/city?type1&sourcehttps%3A%2F%2Fqz-m.oaqhsgl.cn%2Fkf…

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 …

Python MySQL 插入表

Python MySQL 插入表 - 吴吃辣 - 博客园 Python MySQL 插入表 章节 Python MySQL 入门Python MySQL 创建数据库Python MySQL 创建表Python MySQL 插入表Python MySQL SelectPython MySQL WherePython MySQL Order ByPython MySQL DeletePython MySQL 删除表Python MySQL Updat…

jQuery API 中文文档

转载自 jQuery API 中文文档 Ajax 全局 Ajax 事件处理器辅助函数底层接口快捷方法DOM 属性回调对象核心 APICSS数据操作延迟对象弃用 1.3 版本弃用的 API1.4 版本弃用的 API1.7 版本弃用的 API1.8 版本弃用的 API1.9 版本弃用的 API1.10 版本弃用的 API3.0 版本弃用的 API尺寸…

2017济南北大青鸟accp和学士后课程的真实情况

我给大家说说2017年济南北大青鸟培训中心关于accp课程和学士后6.0的课程内容和教学方式的真实教学情况,别的青鸟中心是不是这样我不清楚。  首先是accp课程:面向高中起点,学期1年半,共三个学期,每个学期分别交学费;其次是学士后…