我正在阅读 Wikipedia 上的C#条目,并遇到:
托管内存不能显式释放;相反,它会自动被垃圾收集。
为什么在具有自动内存管理的语言中,甚至不允许手动管理?我可以看到在大多数情况下它是没有必要的,但是当你内存紧张并且不想依赖 GC 的智能时它会派上用场吗?
我正在阅读 Wikipedia 上的C#条目,并遇到:
托管内存不能显式释放;相反,它会自动被垃圾收集。
为什么在具有自动内存管理的语言中,甚至不允许手动管理?我可以看到在大多数情况下它是没有必要的,但是当你内存紧张并且不想依赖 GC 的智能时它会派上用场吗?
具有自动内存管理的语言旨在提供实质性的内存安全保证,这是在存在任何手动内存管理的情况下无法提供的。
预防的问题包括
free()
小号free()
指向不属于你的内存的指针,导致在其他地方非法访问free()
不是分配函数返回值的指针,例如获取堆栈上或数组中间的某个对象的地址或其他分配。free()
d的内存的指针此外,当 GC 将活动对象移动到合并区域时,自动管理可以带来更好的性能。这提高了引用的局部性,从而提高了缓存性能。
垃圾收集通过保证内存分配永远不会别名来强制内存分配器的类型安全。也就是说,如果一块内存当前被视为 type T
,内存分配器可以保证(通过垃圾回收)当该引用处于活动状态时,它将始终引用 a T
。更具体地说,这意味着内存分配器永远不会将该内存作为不同的类型返回。
现在,如果内存分配器允许手动free()
并使用垃圾收集,它必须确保您的内存free()
不被其他任何人引用;换句话说,您传入的free()
引用是对该内存的唯一引用。大多数情况下,如果任意调用 ,这样做的代价非常高昂free()
,因此大多数使用垃圾回收的内存分配器都不允许这样做。
这并不是说不可能。如果你可以表达一个单一的引用类型,你可以手动管理它。但到那时,要么停止使用 GC 语言,要么根本不用担心它会更容易。
调用GC.Collect
几乎总是比使用显式free
方法更好。调用free
只对从无处引用的指针/对象引用有意义。这是容易出错的事情,因为您有可能调用free
错误类型的指针。
当运行时环境为您进行引用计数监视时,它知道哪些指针可以安全地释放,哪些不能,因此让 GC 决定可以释放哪些内存可以避免丑陋的漏洞类漏洞。可以想到一个运行时实现,GC
其中free
显式调用free
单个内存块可能比运行一个完整的内存块要快得多GC.Collect
(但不要期望“手动”释放每个可能的内存块比 GC 更快)。但我认为 C#、CLI(以及其他带有垃圾收集器的语言,如 Java)的设计者已经决定在这里更倾向于稳健性和安全性而不是速度。
在允许手动释放对象的系统中,分配例程必须搜索已释放内存区域的列表以找到一些空闲内存。在基于垃圾收集的系统中,任何立即可用的空闲内存都将位于堆的末尾。通常,系统忽略堆中间未使用的内存区域比尝试分配它们更快、更容易。
我不能说这是答案,但我想到的是,如果可以的话free
,你可能会不小心free
将指针/引用翻倍,或者更糟糕的是,在空闲后使用一个。这违背了使用 c#/java/etc 等语言的要点。
当然,一种可能的解决方案是让您free
通过引用来获取它的论点,并null
在释放后将其设置为。但是,如果他们通过r-value
这样的:free(whatever())
. 我想你可能对 r-value 版本有一个重载,但甚至不知道 c# 是否支持这样的东西:-P。
最后,即使这样也是不够的,因为正如已经指出的那样,您可以对同一个对象有多个引用。将一个设置为null
不会阻止其他人访问现在已释放的对象。
如果您处于“不想依赖 GC 的智能”的情况,那么很可能您错误地为您的任务选择了框架。在 .net 中,您可以稍微操作 GC(http://msdn.microsoft.com/library/system.gc.aspx),在 Java 中不确定。
我认为您不能免费调用,因为您开始执行一项 GC 任务。当 GC 以它认为最好的方式做事并在它决定时做这些事情时,它可以以某种方式总体上保证 GC 的效率。如果开发人员会干扰 GC,它可能会降低其整体效率。
有趣的是,您确实可以通过 System.GC 访问垃圾收集器——尽管从我读过的所有内容来看,强烈建议您允许 GC 自行管理。
曾经有人建议我使用第 3 方供应商的以下 2 行来处理 DLL 或 COM 对象或类似对象的垃圾收集问题:
// Force garbage collection (cleanup event objects from previous run.)
GC.Collect(); // Force an immediate garbage collection of all generations
GC.GetTotalMemory(true);
也就是说,除非我确切地知道引擎盖下发生了什么,否则我不会打扰 System.GC。在这种情况下,第 3 方供应商的建议“修复”了我正在处理的关于他们的代码的问题。但我不禁想知道这是否真的是他们损坏代码的解决方法......
许多其他答案很好地解释了 GC 的工作原理以及在针对提供 GC 的运行时系统进行编程时应该如何思考。
我想添加一个技巧,在使用 GC 语言进行编程时要牢记。规则是“尽快放下指针很重要”。通过删除指针,我的意思是我不再指向我不再使用的对象。例如,这可以在某些语言中通过将变量设置为 Null 来完成。这可以看作是对垃圾收集器的提示,只要没有其他指向它的指针,就可以收集这个对象。
为什么要使用free()
?假设您有一大块要释放的内存。
一种方法是调用垃圾收集器,或者让它在系统需要时运行。在这种情况下,如果无法访问大块内存,它将被释放。(现代垃圾收集器非常聪明。)这意味着,如果它没有被释放,它仍然可以被访问。
free()
因此,如果您可以使用垃圾收集器而不是垃圾收集器来摆脱它,那么仍然可以访问该块(如果语言有这个概念,则不能通过弱指针),这意味着您只剩下该语言的等价物了悬空指针。
该语言可以保护自己免受双重释放或试图释放未分配的内存,但它可以避免悬空指针的唯一方法是废除free()
或修改其含义,使其不再有用途。
为什么在具有自动内存管理的语言中,甚至不允许手动管理?我可以看到在大多数情况下它是没有必要的,但是当你内存紧张并且不想依赖 GC 的智能时它会派上用场吗?
在绝大多数垃圾收集语言和 VM 中,提供一个函数是没有意义的,free
尽管如果您愿意,几乎总是可以使用 FFI 在托管 VM 之外分配和释放非托管内存。
free
垃圾收集语言中不存在的主要原因有两个:
关于内存安全,自动内存管理背后的主要动机之一是消除由不正确的手动内存管理引起的错误类别。例如,手动内存管理free
使用相同的指针调用两次或使用不正确的指针可能会破坏内存管理器自己的数据结构,并在程序后期导致非确定性崩溃(当内存管理器下一次到达其损坏的数据时)。自动内存管理不会发生这种情况,但暴露free
会再次打开这个蠕虫罐。
关于指针,该free
函数在由指针指定的位置释放一块分配的内存,返回内存管理器。垃圾收集语言和虚拟机用更抽象的概念替换指针,称为引用。大多数生产 GC 都在移动,这意味着高级代码持有对值或对象的引用,但内存中的底层位置可能会发生变化,因为 VM 能够在高级语言不知道的情况下移动分配的内存块。这用于压缩堆,防止碎片和改善局部性。
free
因此,当您拥有 GC 时,有充分的理由不拥有。
允许手动管理。例如,在 Ruby 中,调用GC.start
将释放所有可以释放的东西,尽管你不能单独释放东西。