在实时系统中控制 Java 垃圾收集

Posted

技术标签:

【中文标题】在实时系统中控制 Java 垃圾收集【英文标题】:Controlling Java garbage collection in real-time system 【发布时间】:2016-10-26 20:39:20 【问题描述】:

我们正在用 Java 运行一个 RT 系统。它通常使用相对较大的堆(100+GB)并为来自消息队列的请求提供服务。必须快速处理每个请求(

我们遇到了严重的 GC 相关问题,因为经常发生 GC 在请求期间(200+ms)导致 stop-the-world 收集,从而导致失败。

我们的一位对 GC 有一定了解的开发人员花了很多时间调整 GC 参数并尝试不同的 GC。几天后,他提出了一些我们戏称“由遗传算法进化”的参数化。它降低了 GC 暂停,但仍远未满足 SLA 要求。

我正在寻找的解决方案是保护代码的一些关键部分免受 GC,在请求完成后,让 GC 完成所需的工作,在采取之前下一个请求。请求之外的偶尔暂停是可以的,因为我们有几个工作人员,而垃圾收集工作人员暂时不会请求请求。

我有一些想法很愚蠢,很丑陋,而且很可能不起作用,但希望它们能说明问题:

偶尔在接收线程中调用Thread.sleep(),祈求GC同时做一些工作, 在请求之间调用 System.gc()Runtime.gc(),再次绝望地祈祷它提供帮助, 用https://***.com/a/6915221/1137187 之类的hacky 模式完全弄乱代码。

最后重要的一点是,我们是一家低预算的初创公司,Zing® 等商业解决方案不适合我们,我们正在寻找非商业解决方案。

有什么想法吗?我们会将我们的代码完全重写为 C++(我们一开始并不知道 GC 可能是一个问题而不是解决方案),但是代码库已经太大了,无法做到这一点。

【问题讨论】:

当我听到“实时”这个词时,Java 肯定不是我想到的第一种语言,鉴于选择了 Java,对巨大堆的需求似乎不是好兆头。 无论如何,对于长时间运行的进程中的 GC 问题,实际上只有两种通用方法:(1) 减少产生的垃圾量,以及 (2) 使垃圾更快地收集。如果完整 GC 成本高但不频繁,那么一种替代方法可能是大大减少堆大小。这将需要更频繁的 GC,但每个 GC 都应该更快,因为不能收集那么多垃圾。此外,尽量避免长生命周期的对象,因为它们对于分代 GC 的收集成本更高,除非它们在进程的整个生命周期中都被保留。 另外,注意临时对象。 Java 程序员通过创建和丢弃大量对象比需要更多地依赖 GC 的情况并不少见。他们甚至可能没有意识到他们正在这样做。例如,字符串连接和自动装箱可以在这里做出贡献。基元没有 GC 成本,根据经验,较低级别的 API 产生的垃圾更少。 关于保护关键代码免受 GC,Java 没有提供直接适用于该目标的 API。如果是这样,它的使用将引入关键代码将失败并出现OutOfMemoryError 的风险,否则它只会被 GC 延迟。那会造成更大的混乱。 @Tregoreg,大HashMap的图中几乎所有对象都与条目相关联;无论您是否重复使用地图,它们都会变成垃圾。如果这些是导致问题的重要原因,那么想出一种用double[]s 替换它们的方法可能会有所帮助,前提是这样做不需要在其他地方创建与保存的一样多的额外对象地图。 【参考方案1】:

有什么想法吗?

使用不同的 JVM? Azul 声称能够处理此类案件。 Redhat 和 Oracle 分别为 openjdk 贡献 shenandoah 和 zgc,目的相似,所以如果你不想要商业解决方案,也许你可以尝试实验性构建。

还有其他 JVM 专注于实时应用程序,但据我了解,它们专注于较小系统上更严格的实时要求,而您的听起来更像是软实时要求。

您可以尝试的另一件事是通过使用预分配对象或更紧凑的数据表示(如果适用)来显着减少对象分配(分析您的应用程序!)。在保持新代大小相同的同时减少分配压力意味着每次收集的死亡率增加,这应该会加快年轻收集的速度。

选择硬件以最大化内存带宽可能也有帮助。

在请求之间调用 System.gc() 或 Runtime.gc(),再次绝望地祈祷它的帮助,

可能在与-XX:+ExplicitGCInvokesConcurrent 结合使用时起作用,否则它将触发带有 CMS 或 G1 的单线程 STW 集合(我假设您正在使用其中之一)。但这种方法似乎很脆弱,需要大量调整和监控。

【讨论】:

我很少尝试System.gc() + -XX:+ExplicitGCInvokesConcurrent,但它确实不起作用,主要是因为System.gc() 运行了几分钟。运行完整的 GC 似乎不是一种选择。是的,我们正在尝试 CMS 和 G1。如果没有其他帮助,将不得不尝试替代 JVM 和分析+重构。但是,考虑得越多,我就越相信告诉 JVM 何时进行收集正是我所需要的。调整 GC 参数原则上不能解决我的问题,不给 JVM 额外的信息。 嗯,您是说暂停时间为 200 毫秒,而您的 SLA 为

以上是关于在实时系统中控制 Java 垃圾收集的主要内容,如果未能解决你的问题,请参考以下文章

如何优化 Haskell 中软实时应用程序的垃圾收集?

MySQL实时性能监控工具doDBA tools

用ElasticSearch,LogStash,Kibana搭建实时日志收集系统

用Kibana+Logstash+Elasticsearch快速搭建实时日志查询 收集与分析系统

CentOS下如何用nmon收集系统实时运行状况

海量日志实时收集系统架构设计与go语言实现