C# GCHandle.Alloc是否分配内存?

C# GCHandle.Alloc是否分配内存?,c#,garbage-collection,C#,Garbage Collection,我正在使用SciTech的.NET内存分析器来降低程序的内存分配率,并减少垃圾收集的频率 令人惊讶的是,根据profiler,最大的分配量似乎来自我正在进行的GCHandle.Alloc调用,以将现有的.NET数组打包到本机OpenGL 我的理解是,调用GCHandle.Alloc并不分配内存,它只是将现有内存固定在托管堆上 我错了还是探查器错了?来自 一个新的GCHandle,用于保护对象不被垃圾回收。当不再需要此GCHandle时,必须免费释放它 所以,即使没有真正的分配,我想在调用Free

我正在使用SciTech的.NET内存分析器来降低程序的内存分配率,并减少垃圾收集的频率

令人惊讶的是,根据profiler,最大的分配量似乎来自我正在进行的GCHandle.Alloc调用,以将现有的.NET数组打包到本机OpenGL

我的理解是,调用GCHandle.Alloc并不分配内存,它只是将现有内存固定在托管堆上

我错了还是探查器错了?

来自

一个新的GCHandle,用于保护对象不被垃圾回收。当不再需要此GCHandle时,必须免费释放它

所以,即使没有真正的分配,我想在调用Free之前,也无法释放预分配


分析器甚至为我分配的每个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();
Console.WriteLine(“Is 64位:{0},IntPtr.Size:{1}”,Environment.is64位进程,IntPtr.Size);
int[][]对象=新int[100000][];
for(int i=0;i
这是一个小程序。如果运行,您将看到一个“空的”
GCHandle
(使用
new GCHandle()
创建的)占用
IntPtr.Size
内存。如果您使用ILSpy查看它,这一点很清楚:它有一个
IntPtr
字段。如果锁定某个对象,则它将占用
2*IntPtr.Size
内存。这可能是因为它必须在CLR表(大小为
IntPtr
)中加上内部
IntPtr

取自

它使用CLR内部构建的专用GC句柄表。您可以使用GCHandle.Alloc()为此表分配一个条目,然后使用GCHandle.Free()再次释放该条目。垃圾收集器只是将此表中的条目添加到它在执行收集时发现的对象图中

任何人都可以看到,您可以自己查看和发现

如果深入研究
GCHandle.Alloc
,您将看到它调用名为
InternalAlloc
的本机方法:

[System.Security.SecurityCritical]  // auto-generated
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[ResourceExposure(ResourceScope.None)]
internal static extern IntPtr InternalAlloc(Object value, GCHandleType type);
深入到CLR代码中,您会看到对
marshallNative::InternalAlloc
的内部调用,最终调用:

hnd = GetAppDomain()->CreateTypedHandle(objRef, type);
它依次调用
ObjectHandle::CreateTypedHandle
->
HandleTable::HndCreateHandle
->
HandleTableCache->TableAllocSingleHandleFromCache
,如果句柄在缓存中不存在,则分配句柄


正如@Antosha纠正我的,调用的地方不是通过
ComDelegate
(实际上从那以后就没什么意义),而是通过。分配不会发生在托管堆上,而是在运行时保留的外部堆上,用于将句柄根管理到GC对象中。托管堆中唯一发生的分配是
IntPtr
,它保存指向表中地址的指针。尽管如此,您仍应确保在完成后调用
GCHandle.Free

它可能试图告诉您是什么阻止托管对象被垃圾收集。是的,这通常是一个GCHandle,用来保持传递给OpenGL的纹理或网格的活力。如果这些是钉住句柄,可能有点糟糕,但这只是您使用的OpenGL包装库的一个方面。探查器甚至为我分配的每个GCHandle分配一个特定的内存量-8字节。托管堆似乎随着每个GCHandle.Alloc增长了8个字节。因此,看起来它实际上在托管堆上分配了空间,尽管我不知道这是为了什么?CLR在GCHandles的存储位置周围保留了一个单独的句柄表。当然,8字节听起来差不多。如果该值在没有绑定的情况下增长,则当不再需要该资产时,您可能缺少所需的GCHandle.Free()调用。@HansPassant如果该值没有无限增长,则我正在释放这些句柄。问题在于,它会导致托管堆增长,并最终触发垃圾回收。这就是我想尽量减少的。你确定你需要固定那么多内存吗?出于自然原因,任何类型的内存固定都会对GC造成压力。这是不正确的
GCHandle.Alloc
调用
MarshalNative::GCHandleInternalAlloc
,而不是
COMDelegate::InternalAlloc
。前者在GC引擎维护的句柄表中创建一条新记录,并返回其地址。该句柄表不是托管堆的一部分,这意味着
GCHandle.Alloc
调用对托管堆大小没有影响。@Antosha感谢您的更正。重新阅读我的回答也清楚地表明,
GCHandle
调用
comdegate
没有什么意义,而且我对运行时的调查是不正确的。我已经更正了我的答案。谢谢你更正了答案;我现在同意。当
GCHandle
存储在托管堆上时(例如,作为对象字段或
GCHandle[]
的元素),它消耗的内存量与
IntPtr
相同,即32位体系结构上消耗4字节,64位体系结构上消耗8字节。调用
GCHandle.Alloc时