11

.NET GC 精要(七)

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/104231908
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

.NET GC 精要(七)

本文讲述了 .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 的内存压缩流程造成影响,使用时应尽量缩短对象的固定时间.

系列文章完


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK