.NET GC 精要(七)

49 篇文章 1 订阅

本文讲述了 .NET GC 的一些细节知识,内容大部分来自于书籍 Under the Hood of .NET Memory Management
(注:本文假设你了解 .NET 的基础知识,譬如值类型,引用类型等)

深入
并发执行模式(工作站模式下)的一点细节

之前讲到工作站模式分为 并发非并发 两种执行模式,其中非并发 执行模式比较容易理解,即在整个 GC 流程中应用线程(application thread)是暂停的(非并发执行模式一般适用于单核运行环境).

而对于并发执行模式,细节上则会复杂一些:

并发执行模式下, Gen 0 回收 和 Gen 1 回收 仍然会暂停应用线程,只有在 Full GC(即 Gen 2 回收)时才会有并发行为,并且在整个 GC 流程中一般只会造成应用线程 2 次(短期)暂停.

相关实现上,由于 Full GC 发生时,需要检查回收的内存范围(称为 GC domain)是确定的,所以应用线程可以在 Full GC 的同时于当前 GC domain 以外的内存范围中 申请对象,当然由于内存段的大小限制, 并发执行 GC 时,内存段上还会被设置特殊的内存区域(称为 No Go Zone),如果应用线程的对象申请达到了这个区域,则应用线程仍然会被暂停.

示意图如下:

在这里插入图片描述

(可以看到,Full GC 过程中,应用线程仍然可以申请对象(Object L, M, N 和 O))

并发执行模式虽然允许应用线程在 Full GC 过程中继续申请对象,但仍然有不少限制(申请对象不能触及 No Go Zone 区域;申请的对象即使不被引用也不能(被本次 GC )回收(譬如上面示意图中的 Object M)),为了解决这个问题, .NET 4.0 引入了 Background Workstation GC, .NET 4.5 甚至引入了 Background Server GC,有兴趣的朋友可以继续了解.

弱引用(Weak References)

对于一些大内存对象,如果每次使用时都进行创建和释放,则程序效率不高,但如果(创建之后)一直保留引用的话,内存消耗又比较大,使用弱引用可以缓解这个问题:

// load a big data structure
var bigDataObject = new BigDataStructure();

// get a weak reference to it
var weakRef = new WeakReference(bigDataObject, false);
// destroy the strong reference, keeping the weak reference
bigDataObject = null;

// ...

// some time later try and get a strong reference back
bigDataObject = (BigDataStructure)weakRef.Target;
// recreate if weak ref was reclaimed
if (bigDataObject == null)
{
    bigDataObject = new BigDataStructure();
}

.NET 中, 弱引用被分为两类:

  • short weak references

对于 short weak references, GC 如果发现其引用对象没有被遍历流程标记,即会清理其引用对象.

创建方式:

// pass false to WeakReference's constructor
var shortWeakRef = new WeakReference(object, false);
  • long weak references

对于 long weak references, GC 如果发现其引用对象没有被遍历流程标记并且不在 Finalization Queue 中,即会清理其引用对象.

创建方式:

// pass true to WeakReference's constructor
var longWeakRef = new WeakReference(object, true);
GCHandle

GCHandle 可以用于追踪对象堆上的 Object ,一大用处就是支持托管程序和非托管程序之间的互操作.

GCHandle 的类型分为 4 种:

  • Normal 用于追踪一般对象
  • Weak 用于追踪 short weak references
  • Weak 用于追踪 long weak references
  • Pinned 用于固定对象的内存地址

以下是互操作的一段示例代码:

var buffer = new byte[512];
var h = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var ptr = h.AddrOfPinnedObject();

// Call native API and pass buffer
// ...

if (h.IsAllocated) 
{
    h.Free();
}

由于非托管程序一般需要保证对象的内存地址不变,所以我们使用 GCHandleType.Pinned 来固定对象的内存地址,值得一提的是,使用 fixed 语句块也会固定对应的对象内存地址:

unsafe static void Main()
{
    Person p = new Person();
    p.age = 25;
    // Pin p
    fixed (int* a = &p.age)
    {
        // Do something
    }
    // p unpinned
}

之前提到 SOH 为了解决内存碎片问题会进行内存压缩,但是由于其不能调整固定内存地址的对象,所以使用 GCHandleType.Pinned 会对 SOH 的内存压缩流程造成影响,使用时应尽量缩短对象的固定时间.

系列文章完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值