例如,假设在 C# 4 中包含一个“删除”关键字。由于基于引用的系统,是否可以保证您永远不会有野指针,但仍然能够依赖垃圾收集器?
我可以看到它可能发生的唯一方法是,如果不是对内存位置的引用,而是对指向实际对象的指针表的索引。但是,我确信在某些情况下会破坏,并且有可能破坏类型安全/具有悬空指针。
编辑:我说的不是.net。我只是以 C# 为例。
例如,假设在 C# 4 中包含一个“删除”关键字。由于基于引用的系统,是否可以保证您永远不会有野指针,但仍然能够依赖垃圾收集器?
我可以看到它可能发生的唯一方法是,如果不是对内存位置的引用,而是对指向实际对象的指针表的索引。但是,我确信在某些情况下会破坏,并且有可能破坏类型安全/具有悬空指针。
编辑:我说的不是.net。我只是以 C# 为例。
你可以 - 有点:让你的对象一次性,然后自己处理。
手动删除不太可能提高托管环境中的内存性能。它可能有助于非托管资源,处置的全部内容。
我宁愿更容易实现和使用 Disposable 对象。我不知道这应该是什么样子,但是在 .NET 下管理非托管资源是一个冗长的痛苦。
实现删除的一个想法:删除标记一个手动删除的对象。在下一个垃圾回收周期中,该对象被删除,所有对它的引用都设置为 null。
起初听起来很酷(至少对我而言),但我怀疑它是否有用。这也不是特别安全 - 例如,另一个线程可能正忙于执行该对象的成员方法,例如在访问对象数据时需要抛出这样的方法。
使用垃圾回收,只要您有对对象的引用引用,它就会保持活动状态。使用手动删除,您无法保证。
示例(伪代码):
obj1 = new instance;
obj2 = obj1;
//
delete obj2;
// obj1 now references the twilightzone.
简而言之,将手动内存管理与垃圾收集相结合违背了 GC 的目的。此外,何苦呢?如果你真的想拥有控制权,请使用 C++ 而不是 C#。;-)。
你能得到的最好的结果是划分成两个“半球”,其中一个半球被管理,并且可以保证没有悬空指针。另一个半球有明确的内存管理并且不提供任何保证。这两者可以共存,但是不行,你不能给第二半球强硬的保证。您所能做的就是跟踪所有指针。如果一个被删除,那么指向同一实例的所有其他指针都可以设置为零。不用说,这是相当昂贵的。您的表会有所帮助,但会引入其他成本(双重间接)。
我的第一反应是:为什么不呢?我无法想象您想要做的事情只是在堆上留下一个未引用的块以便稍后再次找到它。好像一个指向堆的四字节指针太多了,以至于无法跟踪这个块。
所以问题不在于分配未引用的内存,而是有意处置仍在引用中的内存。由于垃圾收集在某些时候执行了将内存标记为空闲的功能,因此我们似乎应该能够调用备用指令序列来处理这个特定的内存块。
然而,问题出在这里:
String s = "Here is a string.";
String t = s;
String u = s;
junk( s );
做什么t
和u
指向什么?在严格的参考体系中,t
应该u
是null
. 所以这意味着你不仅要进行引用计数,还可能要进行跟踪。
但是,我可以看到您应该在代码中完成s
这一点。因此junk
可以将引用设置为 null,并使用某种优先级代码将其传递给清扫器。gc 可以被激活以进行有限的运行,并且只有在无法访问时才释放内存。所以我们不能明确地释放任何人已经编码以再次以某种方式使用的东西。但是如果s
是唯一的引用,那么块就会被释放。
所以,我认为它只能在有限地遵守明确的一面的情况下工作。
Chris Sells 也在 .NET Rocks 上讨论了这个问题。我想那是在他第一次露面时,但在后来的采访中可能会重新讨论这个话题。
在 C++ 等非托管语言中,这是可能的,并且已经实现。基本上,您实现或使用现有的垃圾收集器:当您需要手动内存管理时,您可以正常调用 new 和 delete,而当您需要垃圾收集时,您可以调用 GC_MALLOC 或任何用于垃圾收集器的函数或宏。
有关示例,请参见http://www.hpl.hp.com/personal/Hans_Boehm/gc/ 。
由于您使用 C# 作为示例,因此您可能只想在托管语言中实现手动内存管理,但这是为了向您展示相反的情况是可能的。
这将有助于具有长寿命对象的情况。当对象在短时间内使用并快速取消引用时,垃圾收集工作得很好。问题是当某些对象存在很长时间时。清理它们的唯一方法是执行资源密集型垃圾收集。
在这些情况下,如果有一种方法可以显式删除对象,或者至少有一种方法可以将对象图移回第 0 代,事情就会变得容易得多。
如果对象引用上的删除语义将使引用该对象的所有其他引用为空,那么您可以使用 2 级间接(比您提示的多 1 级)来执行此操作。尽管请注意,虽然底层对象将被销毁,但必须在堆上保持固定数量的信息(足以保存引用)。
用户使用的所有引用都会引用真实对象的隐藏引用(可能存在于堆中)。当对对象进行某些操作时(例如调用方法或依赖其标识,例如使用 == 运算符),程序员使用的引用将取消引用它指向的隐藏引用。删除对象时,实际对象将从堆中删除,隐藏引用将设置为 null。因此,参考程序员会看到评估为空。
清除这些隐藏的引用将是 GC 的工作。
是的……但有一些滥用。
可以稍微滥用 C# 来实现这一点。如果您愿意使用Marshal
类、StructLayout
属性和unsafe code
,您可以编写自己的手动内存管理器。
您可以在此处找到该概念的演示:Writing a Manual Memory Manager in C#。