我正在使用SciTech提供的.NET Memory Profiler来降低程序的内存分配速率和减少垃圾回收的频率。
但是出乎意料的是,根据分析器的结果,最大的分配量似乎来自于我进行的GCHandle.Alloc调用,以将现有的.NET数组映射到本机的OpenGL中。
我的理解是,调用GCHandle.Alloc不会分配内存,它只是锁定托管堆上的现有内存?
那么,我是错了还是分析器是错了呢?
我正在使用SciTech提供的.NET Memory Profiler来降低程序的内存分配速率和减少垃圾回收的频率。
但是出乎意料的是,根据分析器的结果,最大的分配量似乎来自于我进行的GCHandle.Alloc调用,以将现有的.NET数组映射到本机的OpenGL中。
我的理解是,调用GCHandle.Alloc不会分配内存,它只是锁定托管堆上的现有内存?
那么,我是错了还是分析器是错了呢?
.NET参考源代码可供任何人查看,您可以查看并自行了解。
如果您深入研究 GCHandle.Alloc
,您会发现它调用了一个名为InternalAlloc
的本地方法:
[System.Security.SecurityCritical] // auto-generated
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[ResourceExposure(ResourceScope.None)]
internal static extern IntPtr InternalAlloc(Object value, GCHandleType type);
深入挖掘CLR代码,你会看到对MarshalNative::InternalAlloc
的内部调用,最终会调用到:
hnd = GetAppDomain()->CreateTypedHandle(objRef, type);
这将调用ObjectHandle::CreateTypedHandle
-> HandleTable::HndCreateHandle
-> HandleTableCache->TableAllocSingleHandleFromCache
,如果句柄不存在于缓存中,则会分配该句柄。
正如@Antosha纠正我的,调用的地方并不是通过ComDelegate
(这其实没有多大意义),而是通过MarshalNative
。确实会发生一次分配,但不是在托管堆上,而是运行时为管理指向GC对象的句柄根而保留的外部堆上。唯一在托管堆上发生的分配是保存指向表中地址的指针的IntPtr
。尽管如此,完成后仍应确保调用GCHandle.Free
。
GCHandle.Alloc
调用的是 MarshalNative::GCHandleInternalAlloc
,而不是 COMDelegate::InternalAlloc
。前者在由 GC 引擎维护的句柄表中创建一个新记录并返回其地址。该句柄表不是托管堆的一部分,这意味着 GCHandle.Alloc
的调用对托管堆大小没有影响。 - AntoshaGCHandle
来调用 ComDelegate
没有多少意义,而且我对运行时的调查是错误的。我已经更正了我的回答。 - Yuval ItzchakovGCHandle
存储在托管堆上(例如作为对象字段或GCHandle[]
的元素),它会消耗与IntPtr
相同的内存,即在32位架构上为4字节,在64位架构上为8字节。当您调用GCHandle.Alloc
时,句柄变为已分配状态,这还会额外消耗IntPtr.Size
的非托管运行时内存,直到您调用GCHandle.Free
来释放句柄。 - Antosha分析器甚至为我分配的每个GCHandle分配了特定的内存量 - 8字节。而且似乎每次GCHandle.Alloc时托管堆都会增长8字节。所以它似乎确实在托管堆上分配了空间,尽管我不知道为什么?
我不知道一个句柄怎么可能更小 :) 我做了一些测试:
Console.WriteLine("Is 64 bit: {0}, IntPtr.Size: {1}", Environment.Is64BitProcess, IntPtr.Size);
int[][] objects = new int[100000][];
for (int i = 0; i < objects.Length; i++)
{
objects[i] = new int[] { 0 };
}
long w1 = Environment.WorkingSet;
GCHandle[] handles = new GCHandle[objects.Length];
for (int i = 0; i < handles.Length; i++)
{
//handles[i] = new GCHandle(handles);
//handles[i] = GCHandle.Alloc(objects[i]);
handles[i] = GCHandle.Alloc(objects[i], GCHandleType.Pinned);
}
Console.WriteLine("Allocated");
long w2 = Environment.WorkingSet;
Console.WriteLine("Used: {0}, by handle: {1}", w2 - w1, ((double)(w2 - w1)) / handles.Length);
Console.ReadKey();
来自msdn。
一个新的GCHandle,用于保护对象免受垃圾回收。当不再需要此GCHandle时,必须使用Free释放它。
因此,即使没有进行实际分配,我想在调用Free之前也不能释放预分配的内容。