垃圾回收器(.net/java)对实时系统是否构成问题?

31

当构建一个需要快速和一致响应的系统时,是否存在垃圾收集器可能造成问题的风险?

我记得多年前听到过许多恐怖故事,其中典型的例子就是动作游戏中,当垃圾收集器在清理时,您的角色会在中途停止跳跃几秒钟。

尽管我们已经走了一段路,但我想知道这是否仍然是一个问题。我阅读了关于 .Net 4 中新垃圾收集器的文章,但它似乎仍然像一个黑匣子,你只能相信一切都会很好。

如果您的系统必须始终快速响应,那么拥有垃圾收集器是否会带来太大的问题?是否更好选择像 c++ 这样的更加控制自己语言?如果最终发现垃圾收集器是个问题,那么除了等待一个新版本的运行时或尝试进行非常奇怪的操作来影响收集器之外,基本上几乎没有其他办法。

编辑

感谢所有提供的资源。然而,似乎大多数文章/自定义 GC /解决方案都涉及 Java 环境。.Net 是否也有调整功能或自定义 GC 的选项?


请参阅http://www.ibm.com/developerworks/java/library/j-rtj4/index.html。 - Aaron Novstrup
在 .NET 的早期版本(1 和 1.1),甚至只是抛出一个异常都会导致明显的暂停。现在这方面也好多了。 - Booji Boy
7
请注意,“实时”具有非常具体的含义。 “实时”不意味着“快速”,而是指系统必须保证在特定时间限制内做出反应。游戏不是“实时”应用程序的示例,机器人控制系统是实时系统的示例。您需要一种特殊的实时操作系统。 - Jesper
1
@Jesper,实时系统中有子类别。硬实时和软实时。你所说的是硬实时,而游戏属于软实时。区别在于——硬实时的失败会导致人员受伤或死亡,而软实时的失败会让人们感到愤怒或沮丧。软实时并不意味着时间控制失败通常是可以接受的。 - eonil
7个回答

20
要准确地说,垃圾收集器对于实时系统是一个问题。更准确地说,使用具有自动内存管理的语言编写实时软件是可能的。
有关在Java中实现实时行为的一种方法,请参见Java实时规范。 RTSJ背后的想法非常简单-不要使用堆。 RTSJ提供了新的Runnable对象变体,可以确保线程不会访问任何类型的堆内存。线程可以访问作用域内存(这里没有什么不寻常的),也可以访问永生内存(它存在于应用程序的整个生命周期中)。永生内存中的变量反复被新值覆盖。
通过使用永生内存,RTSJ确保线程不会访问堆,更重要的是,系统没有垃圾收集器来抢占线程的执行。
有关更多详细信息,请参阅由JPL和Sun发表的论文“黄金门项目:朝着太空任务的实时Java”

3
这两个句子似乎矛盾。你是不是在其中一个句子里漏掉了“不”字? - H H
@Henk,我是有意这样说的。由于其中存在垃圾回收器,所以有些系统不能被视为适用于实时使用。没有垃圾回收器的系统,增加了被视为实时系统的机会。 - Vineet Reynolds
那么,还有哪些基于GC的语言,或者至少是内存管理的语言会有这样的设计呢?OCaml或Go之类的语言是否符合要求? - Didier A.

11

我曾经用Java和.NET编写游戏,从未发现这是一个大问题。我想你的“恐怖故事”可能基于许多年前的垃圾回收器 - 技术确实已经有了很长的进步。

唯一让我因垃圾回收而犹豫使用Java/.NET的是在硬实时约束条件下进行嵌入式编程(例如运动控制器)。

但是,您必须意识到GC暂停,并且以下所有内容都有助于最小化GC暂停的风险:

  • 最小化新对象分配 - 虽然现代GC系统中的对象分配非常快,但它们会对将来的暂停产生贡献,因此应尽量减少。您可以使用技术,例如预分配对象数组、保留对象池或使用未打包的基元类型。
  • 使用专门的低延迟库,例如Javalution,用于频繁使用的函数和数据类型。这些库专为实时/低延迟应用程序设计。
  • 确保在有多个可用版本时使用最佳GC算法。我听说过Sun G1 Collector在低延迟应用程序中表现良好。最佳的GC系统可以同时进行大部分收集,因此垃圾回收不需要长时间停止整个程序。
  • 适当调整GC参数。通常存在总体性能和暂停时间之间的权衡,您可能希望以前者为代价来提高后者。

如果您非常富有,当然可以购买带有硬件GC支持的计算机。 :-)


2
要警惕对象池。Java 和 .Net 使用分代垃圾回收机制,而池化的对象最终会出现在最老的一代中。这可能会强制进行更频繁的完整垃圾回收(与较短的“仅年轻一代”垃圾回收相反)。 - GlennS

5

是的,在实时系统中,垃圾必须以确定性方式处理。

一种方法是在每次内存分配期间安排一定量的垃圾回收时间。这被称为“基于工作的垃圾收集”。其思想是,在没有泄漏的情况下,分配和回收应该成比例。

另一种简单的方法(“基于时间的垃圾收集”)是安排一定比例的时间进行周期性垃圾收集,无论是否需要。

在任一情况下,由于不允许花费足够的时间进行完整的垃圾回收,程序可能会耗尽可用内存。这与非实时系统相反,后者允许暂停所需的时间以便收集垃圾。


4
从理论上讲,垃圾收集器不是一个问题,而是一个解决方案。实时系统很难处理动态内存分配。特别是通常的C函数malloc()和free()不能提供实时保证(它们通常很快,但至少在理论上存在“最坏情况”,可能需要过多的时间)。
事实上,可以构建一个动态内存分配器,它提供实时保证,但这需要分配器做一些重要的工作,尤其是移动RAM中的一些对象。对象移动意味着调整指针(从应用程序代码的角度来看是透明的),此时分配器距离成为垃圾收集器只有一小步之遥。
通常的Java或.NET实现并不提供实时垃圾收集,即没有保证的响应时间,但它们的GC仍然经过了大量优化,并且在大多数情况下响应时间非常短。在正常情况下,非常短的平均响应时间比保证的响应时间更好(“保证”并不意味着“快”)。
此外,请注意,通常的Java或.NET实现运行在非实时操作系统上(操作系统可以决定调度其他线程,或者可能积极地将某些数据发送到交换文件等),底层硬件也不是实时的(例如,典型的硬盘可能会定期进行“重新校准暂停”)。如果您愿意容忍由于硬件原因造成的偶尔时间故障,则使用(经过精心调整的)JVM垃圾收集器应该没有问题。即使对于游戏来说也是如此。

1
这绝对是一个问题。如果你正在编写低延迟应用程序,你不能承受大多数垃圾收集器所施加的“停止世界”暂停。由于Java不允许关闭GC,你唯一的选择就是不产生垃圾。这可以通过对象池和引导来实现,而且已经被实现过了。我写了一篇博客文章详细介绍了这个问题。

1

这是一个潜在的问题,但是...

当操作系统从超负荷的硬盘检索一页内存时,您的字符也可能在C++程序中间冻结。如果您没有在设计用于提供具体性能保证的硬件上使用实时操作系统,则无法保证性能。

要获得更具体的答案,您需要询问特定虚拟机的特定实现。如果垃圾收集虚拟机提供了有关垃圾收集的适当性能保证,则可以将其用于实时系统。


是的,虽然您对此无能为力,但仍然可以采取某些措施。使用垃圾收集器解决问题更加棘手。然而,有很多新信息表明,GC确实提供一些选择和调整选项。 - Toad

0

我们公司正在使用一个基于.Net的大型软件应用程序,其中包括监控现场总线网络上的二进制传感器。在某些情况下,传感器仅激活短时间(300毫秒),但我们的软件仍然需要捕获这些事件,因为当事件被错过时,受控系统将立即失败。最近我们在客户现场观察到由于垃圾收集器运行时间过长(长达1秒),导致问题增加。我们仍在努力找出如何对垃圾收集器强制执行时间限制。总之,在这个简短的故事中,我想说垃圾收集器是时间关键应用程序中的障碍。


3
使用1秒并发现代垃圾收集器?要么.NET没有任何有用的垃圾收集器,要么您做错了某些事情(代码风格或配置)。在Java世界中,几个JVM / GC具有更好的性能。Azul即使对于数百GB的大堆也能实现毫秒级GC暂停。IBM针对较小的堆有一些更低的目标(搜索“metronome”)。 - Voo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接