垃圾收集为什么?为什么不编译器自动插入free()呢?

时间:2016-01-21 04:42:36

标签: compiler-construction garbage-collection

为什么我们不让编译器在适当的位置自动插入free(),而不是在运行时定期运行垃圾检测?这样,我们只在编译时支付一次价格。

编译器知道变量超出范围或重新分配给不同对象的位置。因此,它可以找出对象是否不再可访问,并在那里自动插入free()。​​

可以吗?为什么呢?

如果是因为多线程,我们可以用单线程/绿线程语言来做吗?

3 个答案:

答案 0 :(得分:3)

  

编译器知道变量超出范围或重新分配给不同对象的位置。

确实如此 - 对于变量。但是你没有清除变量 - 你清除它们指向的内存。只是因为变量超出范围,并不意味着指向的内存不再可达。

例如:

UPDATE objects
SET body = jsonb_set(body, '{name}', '"Mary"', true)
WHERE id = 1; 

您无法做出编译时决定是否应该在该段的最后一行释放x指向的内存,因为它取决于该代码为

因此,为了解决这个问题,必须通过插入适当的逻辑将此决定委托给运行时,例如:

y = ...
{
  x = new X();
  if (todayIsTuesday()) {
    y = x;
  }
} // x just went out of scope

如果Y* y = ... { X* mem = new X(); X* x = mem; markPointer(&x, mem); if (todayIsTuesday()) { y = x; markPointer(&y, mem); } markNoLongerPointer(&x, mem); } // x just went out of scope 内部维护的数据结构告诉它markNoLongerPointer()是对该内存的唯一引用,那么x清除作为第二个参数给出的内存...换句话说,这就是原始内容起点指向参考计数逻辑。

编译器当然可以将这样的引用计数逻辑添加到已编译的代码中,有些人会这样做,但正如其他人所提到的,引用计数有一些缺点:高开销,周期问题,加上它有时会在不方便的时候造成重大暂停,当对大型数据结构的根的唯一引用超出范围时。但是,有一些方法可以解决其中一些缺点,但这个答案超出了范围: - )

答案 1 :(得分:1)

正如您可以在此Wikipedia article on Reference Counting中了解更多信息,引用计数而不是垃圾收集有两个主要缺点:

  1. 维护引用的开销:从性能的角度来看,这是相当可观的。每当任何其他对象引用另一个对象或引用超出范围时,JVM将需要递增和/或递减计数。此外,它还会产生更多空间,以维持参考计数;每个对象至少会消耗一个额外的四字节整数。
  2. 周期性参考问题:这似乎是最大的原因。 Java中使用的大量肉和土豆数据结构具有周期性参考。想到的第一个用例是链表中的节点。对于现实问题和更深奥的数据结构,问题变得更加复杂。

答案 2 :(得分:1)

从根本上说,在非平凡的资源管理案例中,程序员显然必须free内存(和其他资源)。它可能与free的调用完全不同;它可能是从列表中删除对象引用,以便垃圾收集器可以收集它,或将其设置为空引用,或释放智能指针,或从列表中删除对象,或者对{{1}的真正调用}。但是,程序员仍然必须以某种方式明确地指定它,以避免逻辑泄漏。

在我们编译器能够开始阅读我们的思想并确定我们正在制作什么类型的软件或者我们开始以完全不同的方式编写代码之前,这无法在编译时确定。因为想象一下像Photoshop这样的图像编辑器。什么时候应该释放图像?当用户关闭它时。这对我们人类来说是显而易见的,但它不是信息编制者所拥有的。

数字音频工作站的类似情况。应该何时从内存中释放音频片段?当用户将其从剪辑编辑器中删除时,例如对于人类设计师来说,这是显而易见的,而不是编译器。我的80年代音乐收藏什么时候应该从我的硬盘中删除以节省空间?当我,用户明确删除它们时。在软件可以开始可靠地阅读我的思想之前,它不能比手动/显式更少。

因此,对于这些非平凡的案例,无论是垃圾收集还是RAII还是其他任何事情,都没有自动资源管理。在这种情况下,程序员必须始终明确free资源以响应正确的输入/事件,无论因为它是人类的想法和设计非常特定于应用程序的域,以确定何时应该释放这些资源。

当然,对于像函数的给定范围本地分配的内存等微不足道的情况,编译器可以自动释放它而不需要像垃圾收集这样的任何东西,而且它们可以。即使在C:

free

......而且C基本上与你所建议的类似。它将生成增加/减少堆栈指针的指令,例如,有效地分配和释放存储器并使释放的存储器可用于其他地方。使用C ++,它甚至可以在使用RAII时为堆分配的内存执行此操作,前提是拥有该内存的对象在退出函数范围时变得无法访问。但是这些类型的临时资源的生命周期与具有类似堆栈的分配/释放推送/弹出模式的函数的范围相关联是微不足道的情况。非平凡的情况总是要求程序员以某种方式明确指定何时不再需要资源,因为非平凡的情况处理持久状态,其生存期与任何给定函数的范围无关。