在tenured堆空间中处理许多符合GC的大型对象

时间:2014-07-16 13:51:34

标签: java garbage-collection

我有一个生成大型结果对象并将它们放入队列的应用程序。多个工作线程创建结果对象并对其进行排队,并且单个写入器线程将对象降级,将它们转换为CSV,并将它们写入磁盘。由于I / O和结果对象的大小,编写结果所需的时间比生成它们要长得多。这个应用程序一个服务器,它只是一个命令行应用程序,它运行大量的请求和完成。

我想减少应用程序的总内存占用量。使用堆分析工具(IBM HeapAnalyzer),我发现在程序终止之前,大多数大型结果对象仍然在堆上,即使它们已被排队并且没有其他引用它们。也就是说,它们都是根对象。它们占据了堆空​​间的大部分。

对我而言,这意味着当他们仍然在队列中时,他们将它变成了终身堆空间。由于在运行期间没有触发完整的GC,因此它们仍然存在。我意识到它们应该是终身受让的,否则我会在Eden空间里来回复制它们,而它们仍在队列中,但同时我希望我能做些什么来帮助摆脱它们之后退队,没有打电话System.gc()

我意识到摆脱它们的一种方法是简单地缩小最大堆大小并触发完整的GC。但是,该程序的输入大小差异很大,我希望所有运行都有一个-Xmx设置。

为澄清添加:这是一个问题,因为在Eden中实际编写对象时也存在大量内存开销(主要是String个实例,这些实例也显示为根堆分析)。因此,伊甸园经常出现轻微的GC。如果结果对象没有在终身空间中闲逛,则这些频率会降低。可以说我的真正问题是伊甸园的输出开销,我正在努力解决这个问题,但希望同时追究这个终身问题。

在我研究这个时,是否有任何特定的垃圾收集器设置或程序化方法我应该关注?注意我使用的是JDK 1.8。

回答更新:@maaartinus提出了一些很好的建议,帮助我避免首先排队(并因此使用)大型对象。他还建议限制队列,这肯定会减少我现在正在排队的时间(结果对象的CSV byte[]表示)。线程计数和队列边界的正确组合肯定会有所帮助,尽管我没有尝试过这个问题,因为问题基本上消失了,因为它找到了一种不首先使用大对象的方法。

1 个答案:

答案 0 :(得分:2)

我对与GC相关的解决方案持怀疑态度,但看起来你正在创造一个你不需要的问题:

  

多个工作线程创建结果对象并对其进行排队,以及单个编写器......

     

...写出结果所需的时间比生成它们要长得多......

所以看起来它实际上应该是另一回事:单一制作人和许多消费者保持游戏均匀。

多个作家可能不会给你太多加速,但如果可能的话,我会尝试一下。只要你的结果使用有界队列,生产者的数量并不重要(我假设他们没有大量的输入,因为你没有提到它)。这个有界的队列还可以确保对象永远不会太旧。

在任何情况下,您都可以使用多个CSV转换器,因此可以用大Stringbyte[]ByteBuffer或其他任何方式(假设您想要)替换大对象在内存中进行转换)。关于缓冲区的好处是你可以回收它(所以它得到终身不再是问题)。

你也可以使用一些非托管内存,但我真的不相信它是必要的。简单地限制队列就足够了,除非我遗漏了什么。

顺便说一下,最便宜的解决方案往往是购买更多内存。真的,一小时的工作值几千兆字节。

更新

  

我应该担心多个编写器线程之间的争用多少,因为他们都会共享一个线程安全的Writer?

我可以想象两种问题:

  • 原子性:虽然同步确保每个执行的操作都是原子地进行的,但这并不意味着输出有意义。想象一下多个编写器,每个编写器生成一个CSV,结果文件应包含所有CSV(按任意顺序)。使用PrintWriter会使每一行保持完整,但它会混合它们。

  • 并发:例如,FileWriter执行从charbyte的转换,这可能在此上下文中以同步块结束。这可能会减少并行性,但由于IO似乎是瓶颈,我猜,这没关系。