大多数程序员都认为垃圾收集是一件好事,在大多数应用程序中都非常值得花费。但是,我个人的观察是,大多数对象的内存管理都是微不足道的,并且可能有10%-20%的内存管理需要考虑诸如引用计数和一般非常复杂的内存管理方案等问题。在我看来,只需一小部分开销就可以获得垃圾收集的所有好处,保守地手动删除大对象,其中对象的生命周期很明显,让GC收集其余的,假设GC实现支持这样的事情。这将允许GC运行频率降低,并消耗更少的多余内存,同时仍然避免实际难以手动管理的情况。更有趣的是,如果编译器在生命周期显而易见的情况下自动插入确定性删除语句:
int myFunc() {
Foo[] foo = new Foo[arbitraryNumber]; // May be too big to stack allocate.
// do stuff such that the compiler can prove foo doesn't escape.
// foo is obviously no longer needed, can be automatically deleted here.
return someInteger;
}
当然,这可能不适用于复制GC,但是为了这篇文章,让我们假设我们的GC没有复制。为什么这种混合内存管理方案在主流编程语言中显然如此罕见?
答案 0 :(得分:3)
因为这种情况太罕见了。几乎没有方法被孤立。它们都接受来自外部的对象或创建对象并将其传递出去。
不访问任何字段,没有参数且不返回某些内容的方法无法执行任何操作。
相反,GCs专注于最常见的案例(90%),并试图控制那些90%(短期临时对象)。这意味着在常见情况下,您需要检查的对象较少,其余的并不重要。接下来,您使用增量扫描(因此您可以在少量冲刺中运行,只会在短时间内中断)。
我曾经试图想出一个更好的GC算法并且失败了。他们使用的方法与奥术相邻。关于Java 5 GC Performance Tuning的文件应该会给你一些想法。当然还有GC article in Wikipedia。
归结为:使用GC甚至可以比传统的内存分配和释放模式更快。想想经典算法,它只是定位任何可到达的对象并将其复制到新的位置。如果你刚忘记了很多对象(比如所有已分配对象的90%),这个算法只需要检查其余的对象(10%)。任何无法达到的东西,无论多么重要,都无关紧要。现在你可能会说复制很昂贵但是a)这不是真的;今天平均桌面CPU可以在不到100ms的时间内复制40MB; b)复制将保护您免受碎片影响,因此它实际上是好东西。
答案 1 :(得分:2)
“大多数程序员都认为垃圾收集是一件好事,在大多数应用程序中非常值得花费。”
全面概括......
答案 2 :(得分:1)
关于“显然不再需要”的快速说明:这并不容易;)
[...]
Foo() {
someGlobalList.add(this);
}
[...]
除此之外,你能够手动删除大东西的想法是一个很好的想法。据我了解,它至少部分是用现代语言实现的(例如C#中的using
,遗憾的是它实际上并不是免费的)。但是,没有达到你想要的程度。
答案 3 :(得分:1)
您描述为非转义对象的确定性删除语句现代GC实现相当有效。大多数已分配的对象都是从池中分配的,并且在方法退出时非常有效地被删除 - 很少有人会在较慢的GC堆上结束。
这实际上产生的效果与您描述的程序员干预程度相同。并且因为垃圾收集器是自动的,所以允许较少的人为错误范围(如果您删除了仍然保留引用的内容,那该怎么办)。
答案 4 :(得分:0)
如果您正在创建一个对象,您希望它一次仅在一个方法调用的上下文中使用,并且没有最终确定,那么我建议在使用的语言中使用值类型而不是引用类型这种区别。例如,在C#中,您可以将实体声明为struct
而不是class
。然后,您的短生命周期方法本地实例将在堆栈而不是堆上分配,并且在方法返回时它们将被释放。[/ p>
这种技术可以进一步说明你原来的想法,因为结构可以传递给其他方法而不用担心会破坏生命周期分析。
对于数组,如问题所示,您可以使用stackalloc
命令来实现此效果。
答案 5 :(得分:0)
这似乎正是D管理内存的方式。收集垃圾,同时允许您在需要时专门删除对象,或者甚至将GC全部避开,以支持malloc / free。 scoped关键字似乎在堆栈上执行您想要的操作,但我不确定它是否实际在堆栈或堆上分配对象。
答案 6 :(得分:0)
虽然将手动清理与垃圾收集相结合可能会带来一些好处,但是让垃圾收集器在不再需要它们的时间和可以显示没有幸存的时间之间继续管理对象有很大的好处。有根参考。除此之外,在非GC系统中,通常很难证明何时删除对象时,任何引用都不可能存在。如果在引用仍然存在时删除了某个对象,则尝试使用该引用可能会导致任意未定义的行为;在没有GC的情况下防止这种危险通常很困难。相反,如果一个对象完成后会使对象无效,但是将以前占用的内存重用到GC,则可以确保尝试使用无效对象将以可预测的方式失败。
顺便说一句,如果我正在设计一个“微框架”,我会为可变和不可变对象分别拥有堆区域。如果代码可以判断自收集gen0或gen1以来是否已写入对象,则分代垃圾收集效果最佳。使用可变对象进行这样的确定比使用不可变对象要困难得多。另一方面,手动管理可变对象的使用寿命通常比管理不可变对象的使用寿命更容易,因为前者通常应该有一个明确的“所有者”,而后者通常不会。