通过确定性我隐约意味着可用于航空航天飞行软件等关键实时软件。垃圾收集器(以及动态内存分配)在飞行软件中是很大的禁忌,因为它们被认为是非确定性的。但是,我知道正在对此进行研究,所以我想知道这个问题是否已经解决了。
我还在问题中包括任何垃圾收集算法,这些算法限制了它们的使用方式。
答案 0 :(得分:17)
我知道我可能会为这个回复获得很多下注,但如果你已经在尝试首先避免动态内存,因为你说它是禁止的,你为什么要使用GC呢?我永远不会在可预测的运行时速度是主要问题的实时系统中使用GC。我会尽可能地避免使用动态内存,因此有很多非常少的动态对象可供使用,然后我会手动处理极少数动态分配,因此我可以100%控制某些内容何时发布以及它在哪里释放。毕竟不仅GC不是确定性的,free()与malloc()一样具有确定性。没有人说free()调用必须将内存标记为空闲。它也可以尝试将free'd周围的较小的空闲内存块与一个较大的空闲内存块组合在一起,这种行为不是确定性的,也不是运行时的(有时是free会不会这样做而malloc会在下一次执行分配,但无处写,免费不得这样做。)
在一个关键的实时系统中,你甚至可以用不同的实现替换系统标准malloc()/ free(),甚至可以编写自己的实现(它不像听起来那么难!我之前就已经做过了为了它的乐趣,这是最确定的。对我来说,GC是一个简单方便的东西,它是让程序员远离专注于复杂的malloc()/ free()规划,而是让系统自动处理。它有助于快速进行软件开发,并节省数小时的调试工作查找和修复内存泄漏。但就像我从未在操作系统内核中使用GC一样,我也决不会在关键的实时应用程序中使用它。
如果我需要更复杂的内存处理,我可能会编写自己的malloc()/ free(),它可以按照需要运行(并且最具确定性)并在其上编写我自己的引用计数模型。引用计数仍然是手动内存管理,但比使用malloc()/ free()更舒服。它不是超快,但确定性(至少增加/减少ref计数器在速度上是确定性的)除非你可能有循环引用,否则如果你在整个应用程序中遵循保留/释放策略,它将捕获所有死记忆。唯一不确定的部分是你不知道调用release是否只会减少ref计数器或真正释放对象(取决于引用计数是否为零),但你可以通过提供一个来延迟实际的释放函数说“releaseWithoutFreeing”,它将ref计数器减1,但即使它达到零,它也不会释放()对象。你的malloc()/ free()实现可以有一个函数“findDeadObjects”,它搜索保留计数器为零的所有对象,这些对象尚未被释放并释放它们(稍后,当你处于不太重要的时候你的代码的一部分,有更多的时间来完成这类任务)。由于这也不是确定性的,你可以限制它可以用于“findDeadObjectsForUpTo(ms)”的时间量,ms是它可以用来查找和释放它们的毫秒数,这个时候会很快回来量子已被使用,因此你不会在这项任务上花费太多时间。
答案 1 :(得分:10)
Metronome GC和BEA JRockit是我所知道的两个确定性GC实现(两者都适用于Java)。
答案 2 :(得分:5)
碰巧正在搜索Stack Overflow并注意到这个相当古老的帖子。
Jon Anderson提到了JamaicaVM。由于这些帖子已经超过4年了, 我认为回应这里发布的一些信息非常重要。我为aicas,JamaicaVM,JamaicaCAR和Veriflux的开发商和营销人员工作。
JamaicaVM确实有一个硬实时垃圾收集器。它完全是先发制人的。最正确 实时操作系统中所需的相同行为。虽然抢先等待时间是 CPU速度依赖,假设在Ghz类处理器上垃圾收集器的抢占小于1微秒。有一个32位单一版本,每个进程地址空间最多支持3 GB内存。有一个32位多核版本,每个进程地址空间和多个内核支持3 GB内存。还有64位单核和多核版本,每个进程地址空间支持高达128 GB的内存。垃圾收集器的性能与内存大小无关。为了响应关于运行GC完全内存不足的响应之一,对于硬实时系统,您不会将程序设计为执行此操作。实际上,虽然您可以在这种情况下使用硬实时GC,但您必须考虑到可能无法为您的应用程序接受的最坏情况执行时间。
相反,正确的方法是分析程序以获得最大内存分配,然后将硬实时垃圾收集器配置为在所有先前分配期间逐步释放块,以便所描述的特定方案永远不会发生。这称为线程分布式,工作节奏垃圾收集。
博士。 Siebert关于硬实时垃圾收集器的书描述了如何实现这一点,并提供了一个正式的证据,证明垃圾收集器将跟上应用程序,同时不会成为O(N)操作。
了解实时垃圾收集意味着几件事非常重要:
虽然安全关键型应用程序需要进行硬实时垃圾收集,但它也可用于关键任务和通用Java应用程序。使用硬实时垃圾收集器没有固有的限制。对于一般用途,您可以期望更顺畅的程序执行,因为没有长时间的垃圾收集器暂停。
答案 3 :(得分:3)
对我来说,100%的实时Java仍然是一种非常引人注目的技术,但我并不认为自己是专家。
我建议您阅读这些文章 - Cliff Click blog。他是Azul的架构师,几乎编写了所有标准的1.5 Java并发类等... FYI,Azul专为需要非常大的堆大小的系统而设计,而不仅仅是标准的RT要求。
答案 4 :(得分:2)
它不是GC,但有简单的O(1)固定大小的块分配/免费方案,您可以使用它们进行简单的使用。例如,您可以使用固定大小的块的空闲列表。
struct Block {
Block *next;
}
Block *free_list = NULL; /* you will need to populate this at start, an
* easy way is to just call free on each block you
* want to add */
void release(void *p) {
if(p != NULL) {
struct Block *b_ptr = (struct Block *)p;
b_ptr->next = free_list;
free_list = b_ptr;
}
}
void *acquire() {
void *ret = (void *)free_list;
if(free_list != NULL) {
free_list = free_list->next;
}
return ret;
}
/* call this before you use acquire/free */
void init() {
/* example of an allocator supporting 100 blocks each 32-bytes big */
static const int blocks = 100;
static const int size = 32;
static unsigned char mem[blocks * size];
int i;
for(i = 0; i < blocks; ++i) {
free(&mem[i * size]);
}
}
如果您有相应的计划,可以将设计限制为仅有几个特定尺寸进行动态分配,并为每个可能的尺寸设置free_list。如果你正在使用c ++,你可以实现像scoped_ptr这样简单的东西(对于每个大小,我都使用模板参数)来更简单但仍然是O(1)内存管理。
唯一真正的警告是,你将获得 no 保护免于双重释放,甚至意外地将ptr传递给不是来自获取的释放。
答案 5 :(得分:2)
Sun已经广泛记录了他们的实时垃圾收集器,并提供了可以自己运行的基准here。其他人提到了Metronome,这是另一种主要的生产级RTGC算法。许多其他RT JVM供应商都有自己的实现 - 请参阅我的供应商列表over here,其中大多数都提供了大量文档。
如果您对航空电子设备/飞行软件特别感兴趣,我建议您查看aicas,这是一家专门向航空电子行业推销的RTSJ供应商。 Dr. Siebert(aicas CEO)主页列出了一些学术出版物,这些出版物详细介绍了PERC的GC实施。
答案 6 :(得分:1)
您可能会对以下博士论文感到满意 CMU-CS-01-174 - Scalable Real-time Parallel Garbage Collection for Symmetric Multiprocessors
答案 7 :(得分:1)
实时意味着响应时间的上限。这意味着您可以执行的指令的上限,直到您提供结果。这也为您可以触摸的数据量设置了上限。如果你不知道你需要多少内存,你很可能会有一个计算,你不能给它的执行时间上限。 Otoh,如果你知道你的计算的上限,你也知道它触及了多少内存(除非你真的不知道你的软件做了什么)。因此,您对代码的知识量无需GC。
有些功能(如RT-Java中的区域)允许表达性超出本地和全局(静态)变量。但是他们不会免除你管理你分配的内存的义务,因为否则你不能保证下一个即将到来的分配不会因内存资源不足而失败。
不可否认:我对自称为“实时垃圾收集者”的事情有些怀疑。当然,任何GC都是实时的,如果你假设每个分配运行一个完整的集合(这仍然不能保证它会在之后成功,因为可能发现所有内存块都可以访问)。但对于承诺分配时间较短的任何GC,请考虑其在以下示例代码上的性能:
// assume that on `Link` object needs k bytes:
class Link {
Link next = null;
/* further fields */
static Link head = null;
}
public static void main (String args) {
// assume we have N bytes free now
// set n := floor (N/k), assume that n > 1
for (int i = 0; i < n; i ++) {
Link tmp = new Link ();
tmp.next = Link.head;
Link.head = tmp;
}
// (1)
Link.head = Link.head.next; // (2)
Link tmp = new Link (); // (3)
}
Link
对象将失败),以及所有
到目前为止分配的Link
个对象是
从...开始到达
Link.static Link head
字段。 在第(2)点,
目前 第(3)点,分配应该 因为(2a)成功 - 我们可以使用 曾经是第一个链接 - 但是, 因为(2b),我们必须开始 GC,最终将遍历n-1 对象,因此有一个运行时间 O(N)。
所以,是的,这是一个人为的例子。但是声称具有分配限制的GC也应该能够掌握这个例子。
答案 8 :(得分:1)
我知道这篇文章有点陈旧,但我做了一些有趣的研究,并希望确保更新。
确定性GC可由Azul Systems&#34; Zing JVM&#34;和JRocket。 Zing带有一些非常有趣的附加功能,现在已经基于100%的软件和#34; (可以在x86机器上运行)。目前只适用于Linux ...
价格: 如果您使用的是Java 6,或者之前Oracle正在收取300%的提升并强制支持此功能(每个处理器15,000美元和3,300美元的支持)。据我所知,Azul的价格在10,000美元到12,000美元左右,但物理机器收费,而不是核心/处理器收费。此外,该流程按卷分级,因此您利用的服务器越多,折扣越深。我与他们的谈话表明他们非常灵活。 Oracle是一个永久许可证,Zing是基于订阅的...但是如果你做数学并添加Zing的其他功能(见下面的差异)。
您可以通过迁移到Java 7来降低成本,但随后会产生开发成本。鉴于Oracle的路线图(每18个月左右发布一次新版本),以及它们历史上只提供免费的最新版本和旧版本的Java SE更新这一事实,其中包括#34; free&#34;如果有任何主要版本,预计距离最初的GA版本将有3年的时间。由于最初的GA版本通常不会在生产中被采用12-18个月,并且将生产系统转移到新的主要Java版本通常会带来重大成本,这意味着Java SE支持账单将在初始部署后的6到24个月内开始出现。
显着差异: JRocket在RAM方面仍然有一些可扩展性限制(虽然从几天开始有所改进)。您可以通过一些调整来改善结果。 Zing设计了他们的算法,允许连续,并发,压缩(不停止世界暂停,不需要&#34;调整&#34;需要)。这使得Zing可以在没有理论内存上限的情况下进行扩展(他们正在做300多GB的堆而不会让世界停止或崩溃)。谈论范式转换器(想想对大数据的影响)。 Zing对锁定有一些非常酷的改进,通过一些工作给它带来惊人的性能(如果调整,可以达到亚毫秒的平均值)。最后,他们可以看到生产中的类,方法和线程行为(无开销)。在考虑更新,补丁和错误修复(例如泄漏和锁定)时,我们认为这可以节省大量时间。这实际上可以消除在Dev / Test中重新创建许多问题的需要。
我发现的JVM数据链接:
答案 9 :(得分:0)
我知道azul系统有一个jvm,它的GC是硬件辅助的。它也可以同时运行并快速收集大量数据。
不确定它有多确定。