CLR 的GC分为两个部分,一个是GC的内存分配,另外一个是GC的垃圾回收。这里我们先讲一下垃圾回收。(QQ群:676817308技术讨论群。欢迎您的加入)
一.垃圾回收的触发条件
1.在C#代码中调用 GC.Collection();
2.物理内存不足的情况下
3.分配量超过分配阈值
4.找不到可分配的内存空间
在满足了以上四个条件中的任何一个,CLR都会触发GC的垃圾回收,清理托管堆空间释放内存,以便下次使用。
二.垃圾回收的流程
1停止其它线程到并且切换到抢占模式(抢占模式不可访问托管堆代码,只可访问非托管)
2.重新定位回收的目标代,并且判断是否执行后台GC回收
3-1.如果执行后台GC回收分为两步(第一:后台标记(标记没被使用的托管堆空间),第二:后台清扫(清扫没被使用的托管堆空间))
3-2.如果不执行后台GC,则执行普通GC,普通GC一共分为五个阶段(1,标记。2计划。3.重定位。4.压缩。5清扫。)
4.恢复其它线程到合作模式
以上为GC垃圾回收的全部过程。
三.具体的实施细则
前面提到了垃圾回收的触发条件,第一个条件是直接在C#代码中调用GC.Collection.这个不用多说,人人都会。第二个是物理内存不足,获取物理内存信息调用了vc++标准函数GetProcessMemoryLoad,当获取的信息超过一定值的时候会触发GC回收。代码如下:
void GCToOSInterface::GetMemoryStatus(uint32_t* memory_load, uint64_t* available_physical, uint64_t* available_page_file)
{
uint64_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
if (restricted_limit != 0)
{
size_t workingSetSize;
BOOL status = FALSE;
if (!g_UseRestrictedVirtualMemory)
{
PROCESS_MEMORY_COUNTERS pmc;
status = GCGetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
workingSetSize = pmc.WorkingSetSize;
}
if(status)
{
if (memory_load)
*memory_load = (uint32_t)((float)workingSetSize * 100.0 / (float)restricted_limit);
if (available_physical)
{
if(workingSetSize > restricted_limit)
*available_physical = 0;
else
*available_physical = restricted_limit - workingSetSize;
}
// Available page file doesn't mean much when physical memory is restricted since
// we don't know how much of it is available to this process so we are not going to
// bother to make another OS call for it.
if (available_page_file)
*available_page_file = 0;
return;
}
}
MEMORYSTATUSEX ms;
::GetProcessMemoryLoad(&ms);
if (g_UseRestrictedVirtualMemory)
{
_ASSERTE (ms.ullTotalVirtual == restricted_limit);
if (memory_load != NULL)
*memory_load = (uint32_t)((float)(ms.ullTotalVirtual - ms.ullAvailVirtual) * 100.0 / (float)ms.ullTotalVirtual);
if (available_physical != NULL)
*available_physical = ms.ullTotalVirtual;
// Available page file isn't helpful when we are restricted by virtual memory
// since the amount of memory we can reserve is less than the amount of
// memory we can commit.
if (available_page_file != NULL)
*available_page_file = 0;
}
else
{
if (memory_load != NULL)
*memory_load = ms.dwMemoryLoad;
if (available_physical != NULL)
*available_physical = ms.ullAvailPhys;
if (available_page_file != NULL)
*available_page_file = ms.ullAvailPageFile;
}
}
分配量超过分配阈值的问题,这个是在GC初始化的时候回适当的分配阈值的上下限,当分配的内存超过阈值的上下限的时候也会触发GC的回收。
第0带的阈值下限位0,上线也为6MB左右
第1代的阈值下限位160KB,上限无限制
第二代的阈值下限为 256kb,上限无限制
第二代大对象阈值下限为3MB,上线无限制
至于系数,由于第0代区分服务器和工作战模式所以分别为(20,10以及9,20) 其余代
上下限以及12代大小对象的分别为(2,7 —1.2f,1.8f— 1.25f,4.5f)
1.停止其它线程切换到抢占模式,大致就是停止其它的线程不让其访问托管空间。
FORCEINLINE_NONDEBUG void EnablePreemptiveGC()
{
LIMITED_METHOD_CONTRACT;
#ifndef DACCESS_COMPILE
_ASSERTE(this == GetThread());
_ASSERTE(m_fPreemptiveGCDisabled);
// holding a spin lock in coop mode and transit to preemp mode will cause deadlock on GC
_ASSERTE ((m_StateNC & Thread::TSNC_OwnsSpinLock) == 0);
#ifdef ENABLE_CONTRACTS_IMPL
_ASSERTE(!GCForbidden());
TriggersGC(this);
#endif
// ------------------------------------------------------------------------
// ** WARNING ** WARNING ** WARNING ** WARNING ** WARNING ** WARNING ** |
// ------------------------------------------------------------------------
//
// DO NOT CHANGE THIS METHOD WITHOUT VISITING ALL THE STUB GENERATORS
// THAT EFFECTIVELY INLINE IT INTO THEIR STUBS
//
// ------------------------------------------------------------------------
// ** WARNING **