GCHandle.Alloc 是否分配内存?
Does GCHandle.Alloc allocate memory?
我正在使用 SciTech 的 .NET Memory Profiler 来降低程序的内存分配率和垃圾回收频率。
令人惊讶的是,根据探查器,最大数量的分配似乎来自 GCHandle.Alloc 我正在执行的将现有 .NET 数组编组到本机 OpenGL 的调用。
我的理解是调用GCHandle.Alloc不分配内存,它只是将现有内存固定在托管堆上?
是我错了还是分析器错了?
来自 msdn
A new GCHandle that protects the object from garbage collection. This GCHandle must be released with Free when it is no longer needed.
因此,即使没有进行真正的分配,我想在调用 Free 之前也无法释放预分配。
The profiler is even assigning a specific memory amount to each GCHandle I allocate - 8 bytes. And the managed heap seems to grow 8 bytes with each GCHandle.Alloc. So it seems that it actually does allocate space on managed heap, although I have no idea what for?
我不知道句柄怎么可能更小 :) 我做了一些测试:
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();
这是一个小程序。如果你 运行,你会看到 "empty" GCHandle
(用 new GCHandle()
创建的)占用 IntPtr.Size
内存。如果您使用 ILSpy 查看它,这一点就很清楚了:它只有一个 IntPtr
字段。如果你固定某个对象,那么它会占用 2*IntPtr.Size
内存。这可能是因为它必须在 CLR table(大小 IntPtr
)加上它的内部 IntPtr
中写一些东西
取自
It uses a dedicated table of GC handles built inside the CLR. You allocate an entry into this table with GCHandle.Alloc() and release it again later with GCHandle.Free(). The garbage collector simply adds the entries in this table to the graph of objects it discovered itself when it performs a collection.
.NET reference source任何人都可以看到,你可以自己看一看。
如果你深入研究 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
,它持有指向table中地址的指针。尽管如此,您仍应确保在完成后调用 GCHandle.Free
。
我正在使用 SciTech 的 .NET Memory Profiler 来降低程序的内存分配率和垃圾回收频率。
令人惊讶的是,根据探查器,最大数量的分配似乎来自 GCHandle.Alloc 我正在执行的将现有 .NET 数组编组到本机 OpenGL 的调用。
我的理解是调用GCHandle.Alloc不分配内存,它只是将现有内存固定在托管堆上?
是我错了还是分析器错了?
来自 msdn
A new GCHandle that protects the object from garbage collection. This GCHandle must be released with Free when it is no longer needed.
因此,即使没有进行真正的分配,我想在调用 Free 之前也无法释放预分配。
The profiler is even assigning a specific memory amount to each GCHandle I allocate - 8 bytes. And the managed heap seems to grow 8 bytes with each GCHandle.Alloc. So it seems that it actually does allocate space on managed heap, although I have no idea what for?
我不知道句柄怎么可能更小 :) 我做了一些测试:
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();
这是一个小程序。如果你 运行,你会看到 "empty" GCHandle
(用 new GCHandle()
创建的)占用 IntPtr.Size
内存。如果您使用 ILSpy 查看它,这一点就很清楚了:它只有一个 IntPtr
字段。如果你固定某个对象,那么它会占用 2*IntPtr.Size
内存。这可能是因为它必须在 CLR table(大小 IntPtr
)加上它的内部 IntPtr
取自
It uses a dedicated table of GC handles built inside the CLR. You allocate an entry into this table with GCHandle.Alloc() and release it again later with GCHandle.Free(). The garbage collector simply adds the entries in this table to the graph of objects it discovered itself when it performs a collection.
.NET reference source任何人都可以看到,你可以自己看一看。
如果你深入研究 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
,它持有指向table中地址的指针。尽管如此,您仍应确保在完成后调用 GCHandle.Free
。