简体   繁体   English

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

[英]Dealing with many large GC-eligible objects in tenured heap space

I have an application that produces large results objects and puts them in a queue. 我有一个生成大型结果对象并将它们放入队列的应用程序。 Multiple worker threads create the results objects and queue them, and a single writer thread de-queues the objects, converts them to CSV, and writes them to disk. 多个工作线程创建结果对象并对其进行排队,并且单个写入器线程将对象降级,将它们转换为CSV,并将它们写入磁盘。 Due to both I/O and the size of the results objects, writing the results takes far longer than generating them. 由于I / O和结果对象的大小,编写结果所需的时间比生成它们要长得多。 This application is not a server, it is simply a command-line app that runs through a large batch of requests and finishes. 此应用程序不是服务器,它只是一个运行大量请求和完成的命令行应用程序。

I would like to decrease the overall memory footprint of the application. 我想减少应用程序的总内存占用量。 Using a heap analysis tool ( IBM HeapAnalyzer ), I am finding that just before the program terminates, most of the large results objects are still on the heap, even though they were de-queued and have no other references to them. 使用堆分析工具( IBM HeapAnalyzer ),我发现在程序终止之前,大多数大型结果对象仍然在堆上,即使它们已被排队并且没有其他引用它们。 That is, they are all root objects. 也就是说,它们都是根对象。 They take up the majority of the heap space. 它们占用了大部分堆空间。

To me, this means that they made it into tenured heap space while they were still in the queue. 对我来说,这意味着当他们仍然在队列中时,他们将它变成了终身堆空间。 As no full GC is ever triggered during the run, that is where they remain. 由于在运行期间没有触发完整的GC,因此它们仍然存在。 I realize that they should be tenured, otherwise I'd be copying them back and forth within the Eden spaces while they are still in the queue, but at the same time I wish there was something I could do to facilitate getting rid of them after de-queueing, short of calling System.gc() . 我意识到它们应该是终身受让的,否则我会在Eden空间里来回复制它们,而它们仍在队列中,但同时我希望我能做些什么来帮助摆脱它们之后排队,缺少调用System.gc()

I realize one way of getting rid of them would be to simply shrink the maximum heap size and trigger a full GC. 我意识到摆脱它们的一种方法是简单地缩小最大堆大小并触发完整的GC。 However the inputs to this program vary considerably in size and I would prefer to have one -Xmx setting for all runs. 但是,该程序的输入大小差异很大,我希望所有运行都有一个-Xmx设置。

Added for Clarification : this is all an issue because there is also a large memory overhead in Eden for actually writing the object out (mostly String instances, which also appear as roots in the heap analysis). 为澄清添加 :这是一个问题,因为在Eden中实际编写对象时也存在大量内存开销(主要是String实例,它们在堆分析中也显示为根)。 There are frequent minor GC's in Eden as a result. 因此,伊甸园经常出现轻微的GC。 These would be less frequent if the result objects were not hanging around in the tenured space. 如果结果对象没有在终身空间中闲逛,则这些频率会降低。 The argument could be made that my real problem is the output overhead in Eden, and I am working on that, but wanted to pursue this tenured issue at the same time. 可以说我的真正问题是伊甸园的输出开销,我正在研究这个问题,但我想同时追求这个终身问题。

As I research this, are there any particular garbage collector settings or programmatic approaches I should be focusing on? 在我研究这个时,我应该关注哪些特定的垃圾收集器设置或程序化方法? Note I am using JDK 1.8. 注意我使用的是JDK 1.8。

Answer Update : @maaartinus made some great suggestions that helped me avoid queueing (and thus tenuring) the large objects in the first place. 回答更新 :@maaartinus提出了一些很好的建议,帮助我避免首先排队(并因此使用)大型物体。 He also suggested bounding the queue, which would surely cut down on the tenuring of what I am now queueing instead (the CSV byte[] representations of the results objects). 他还建议限制队列,这肯定会减少我现在正在排队的时间(结果对象的CSV byte[]表示)。 The right mix of thread count and queue bounds will definitely help, though I have not tried this as the problem basically disappeared by finding a way to not tenure the big objects in the first place. 线程计数和队列边界的正确组合肯定会有所帮助,尽管我没有尝试过,因为问题基本上消失了,因为它找到了一种不首先使用大对象的方法。

I'm sceptical concerning a GC-related solution, but it looks like you're creating a problem you needn't to have: 我对GC相关的解决方案持怀疑态度,但看起来你正在创建一个你不需要的问题:

Multiple worker threads create the results objects and queue them, and a single writer... 多个工作线程创建结果对象并将它们排队,并且单个编写器......

... writing the results takes far longer than generating them ... ...写出结果需要比生成它们更长的时间......

So it looks like it should actually be the other way round: single producer and many consumers to keep the game even. 所以它看起来应该是另一回事:单一制作人和许多消费者保持游戏均匀。

Multiple writers mightn't give you much speed up, but I'd try it, if possible. 多个作者可能不会给你太多加速,但如果可能的话,我会尝试一下。 The number of producers doesn't matter much as long as you use a bounded queue for their results (I'm assuming they have no substantially sized input as you haven't mentioned it). 只要你为结果使用有队列,生产者的数量并不重要(我假设他们没有大量的输入,因为你没有提到它)。 This bounded queue could also ensure that the objects get never too old. 这个有界的队列还可以确保对象永远不会太旧。

In any case, you can use multiple to CSV converters, so effectively replacing a big object by a big String or byte[] , or ByteBuffer , or whatever (assuming you want to do the conversion in memory). 在任何情况下,您都可以使用多个CSV转换器,因此可以通过大型Stringbyte[]ByteBuffer或其他任何方式(假设您想在内存中进行转换)有效地替换大对象。 The nice thing about the buffer is that you can recycle it (so the fact that it gets tenured is no problem anymore). 关于缓冲区的好处是你可以回收它(所以它终止的事实不再是问题)。

You could also use some unmanaged memory, but I really don't believe it's necessary. 你也可以使用一些非托管内存,但我真的不相信它是必要的。 Simply bounding the queue should be enough, unless I'm missing something. 简单地限制队列就足够了,除非我遗漏了什么。

And by the way, quite often the cheapest solution is to buy more RAM. 顺便说一句,最便宜的解决方案往往是购买更多内存。 Really, one hour of work is worth a couple of gigabytes. 真的,一小时的工作值几千兆字节。

Update 更新

how much should I be worried about contention between multiple writer threads, since they would all be sharing one thread-safe Writer? 我应该担心多个编写器线程之间的争用多少,因为他们都会共享一个线程安全的Writer?

I can imagine two kinds of problems: 我可以想象两种问题:

  • Atomicity: While synchronizations ensures that each executed operations happens atomically, it doesn't mean that the output makes any sense. 原子性:虽然同步确保每个执行的操作以原子方式发生,但这并不意味着输出有任何意义。 Imagine multiple writers, each of them generating a single CSV and the resulting file should contain all the CSVs (in any order). 想象一下多个编写器,每个编写器生成一个CSV,结果文件应包含所有CSV(按任意顺序)。 Using a PrintWriter would keep each line intact, but it'd intermix them. 使用PrintWriter会使每一行保持完整,但它会混合它们。

  • Concurrency: For example, a FileWriter performs the conversion from char s to byte s, which may in this context end up in a synchronized block. 并发:例如, FileWriter执行从char s到byte s的转换,这可能在此上下文中以同步块结束。 This could reduce parallelism a bit, but as the IO seems to be the bottleneck, I guess, it doesn't matter. 这可能会减少并行性,但由于IO似乎是瓶颈,我猜,这没关系。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM