.Net运行时如何"understand"那个结构还有参考
How does .Net runtime "understand" that structure still has a reference
问题是 .Net 运行时如何理解使用 Marshal.StructureToPtr
放置到内存中的结构字段不得由 GC 释放。
场景下方
我有以下结构:
[StructLayout(LayoutKind.Sequential)]
public struct SomeStruct
{
public string s;
public Stream stream;
public SomeStruct(string s)
{
this.s = s;
this.stream = new MemoryStream(0x100);
}
}
有一种方法可以实例化结构并将其放入内存:
static IntPtr GetStructRawData()
{
IntPtr ptr = Marshal.AllocHGlobal(1024);
Marshal.StructureToPtr(new SomeStruct("hi"), ptr, false);
return ptr;
}
然后我可以从原始内存中创建新的结构:
IntPtr ptr = GetStructRawData();
GC.Collect();
SomeStruct struct2 = (SomeStruct)Marshal.PtrToStructure(ptr, typeof(SomeStruct));
之后发现 struct2
确实包含正确的字符串 ("hi") 和正确的流。所以似乎有对该字符串和 struct1
流的引用。但是引用是什么?运行时如何理解字符串和流不能被收集?
根据 Marc 的评论,我更新了我的答案。
我稍微修改了你的程序并在 WinDbg 中查看它以更好地理解它。在您的示例中,文字字符串以字符串实习 table 为根,这是一个静态字符串,因此它将在进程的生命周期内保持所有文字字符串处于活动状态。
如果我将您的字符串更改为动态创建的,它就不再以此 table 为根。相反,一旦您离开 GetStructRawData
,!gcroot
命令就会报告该字符串不再是根目录。 IE。从 GC 的角度来看,它已经不复存在,它已经不存在了,它失去了生命,它安息了,它是一根前线。
我同意 Marc 的观点,您的程序仍然有效的原因纯属偶然。您可以恢复引用,而对象恰好位于同一位置。不要指望这个。
But what holds the references? How does the runtime understands that the string and the stream must not be collected?
这里的string
有点特例;它实际上是一个 interned 字符串(通过 ldstr
加载),因此它已经被 intern table.
root
MemoryStream
然而...坦率地说,它没有 root。您的代码本质上是错误的和危险的,并且它随时可能会严重失败。可以随时收集或移动(压缩)对象,这会在非托管内存中留下损坏的引用,因为 GC 没有查看非托管内存.
我认为您的代码目前只是 "working" 因为 GC 没有对您进行攻击。还要记住:GC 不会 erase 对象;如果 GC just 决定将 MemoryStream
视为已收集,您可能仍然可以再次与它交谈 而不会抱怨 ,如果内存暂时看起来还不错。但这只是出于错误的原因。
在非托管内存中拥有 引用 是一个可怕的想法,会伤害你。
如果您要使用非托管内存,where T : unmanaged
约束可能是您的救星。它可以防止您陷入这种情况,但作为必要性 会限制您的操作。意思是:你不能有这些字段。
问题是 .Net 运行时如何理解使用 Marshal.StructureToPtr
放置到内存中的结构字段不得由 GC 释放。
场景下方
我有以下结构:
[StructLayout(LayoutKind.Sequential)]
public struct SomeStruct
{
public string s;
public Stream stream;
public SomeStruct(string s)
{
this.s = s;
this.stream = new MemoryStream(0x100);
}
}
有一种方法可以实例化结构并将其放入内存:
static IntPtr GetStructRawData()
{
IntPtr ptr = Marshal.AllocHGlobal(1024);
Marshal.StructureToPtr(new SomeStruct("hi"), ptr, false);
return ptr;
}
然后我可以从原始内存中创建新的结构:
IntPtr ptr = GetStructRawData();
GC.Collect();
SomeStruct struct2 = (SomeStruct)Marshal.PtrToStructure(ptr, typeof(SomeStruct));
之后发现 struct2
确实包含正确的字符串 ("hi") 和正确的流。所以似乎有对该字符串和 struct1
流的引用。但是引用是什么?运行时如何理解字符串和流不能被收集?
根据 Marc 的评论,我更新了我的答案。
我稍微修改了你的程序并在 WinDbg 中查看它以更好地理解它。在您的示例中,文字字符串以字符串实习 table 为根,这是一个静态字符串,因此它将在进程的生命周期内保持所有文字字符串处于活动状态。
如果我将您的字符串更改为动态创建的,它就不再以此 table 为根。相反,一旦您离开 GetStructRawData
,!gcroot
命令就会报告该字符串不再是根目录。 IE。从 GC 的角度来看,它已经不复存在,它已经不存在了,它失去了生命,它安息了,它是一根前线。
我同意 Marc 的观点,您的程序仍然有效的原因纯属偶然。您可以恢复引用,而对象恰好位于同一位置。不要指望这个。
But what holds the references? How does the runtime understands that the string and the stream must not be collected?
这里的string
有点特例;它实际上是一个 interned 字符串(通过 ldstr
加载),因此它已经被 intern table.
MemoryStream
然而...坦率地说,它没有 root。您的代码本质上是错误的和危险的,并且它随时可能会严重失败。可以随时收集或移动(压缩)对象,这会在非托管内存中留下损坏的引用,因为 GC 没有查看非托管内存.
我认为您的代码目前只是 "working" 因为 GC 没有对您进行攻击。还要记住:GC 不会 erase 对象;如果 GC just 决定将 MemoryStream
视为已收集,您可能仍然可以再次与它交谈 而不会抱怨 ,如果内存暂时看起来还不错。但这只是出于错误的原因。
在非托管内存中拥有 引用 是一个可怕的想法,会伤害你。
如果您要使用非托管内存,where T : unmanaged
约束可能是您的救星。它可以防止您陷入这种情况,但作为必要性 会限制您的操作。意思是:你不能有这些字段。