Java Full GC时间过长

7
我有一个Java客户端,从服务器上消耗了大量的数据。如果客户端无法以足够快的速度跟上数据流,则服务器会断开套接字连接。我的客户端每天会断开几次连接。我运行了jconsole查看内存使用情况,堆空间图表呈现出一个相当明显的锯齿形模式,在0.5GB和1.8GB之间波动(分配了2GB的堆空间)。但是每次我在进行完整GC时都会断开连接(但不是在每次完整GC时)。平均而言,我发现完整GC需要超过1秒钟的时间。根据一天中的时间不同,忙时可能每5分钟发生一次完整GC,而在缓慢时期可能会在完整GC之间经过30分钟。

我怀疑如果我能减少完整GC的时间,客户端就可以更好地跟上传入的数据,但我对GC调优没有太多经验。是否有人对此有一些见解,并知道如何做到这一点?还是有其他替代方案也可以起到同样的作用?

** 更新 ** 我使用了-XX:+UseConcMarkSweepGC并且有所改善,但在非常繁忙的时候仍然会断开连接。因此,我将堆分配增加到3GB,以帮助度过繁忙的时刻,现在它似乎运行得很顺畅,但只有1天没有断开连接。也许如果我有时间,我会尝试减少创建的垃圾数量,我相信这也会有所帮助。感谢所有的建议。


1
你能改变连接的超时时间吗? - andre
将堆大小增加到4GB,并观察断开连接和Full GC时间段的模式。 - Alpesh Gediya
更改断开阈值不是一个可行的选择,遗憾地。 - JCB
在Java服务器应用程序中,我看到的一件事是为每个网络读取和复制使用一个新对象。尝试更改为使用单个字节数组分配进行读取,并将字节从那里复制到其他持久字节数组中。当然,我对Java的一个抱怨是大多数人使用代码库,这使得有效地进行这些更改几乎不可能,因为它被深深地埋藏了。 - Zan Lynx
3个回答

15

Full GC可能需要很长时间才能完成,而且不容易进行调整。

简单的一种调整方式是增加堆空间 - 一般来说,将堆空间加倍可以将两次GC之间的间隔加倍,但会使GC所需的时间增加一倍。如果您运行的程序具有非常清晰的使用模式,也许可以考虑增加堆空间,使间隔变得如此之大,以至于您可以保证有一些空闲时间来尝试让系统执行GC。另一方面,按照这种逻辑,如果堆很小,完全垃圾收集将在瞬间完成,但那似乎比帮助更多地邀请麻烦。

此外,-XX:+UseConcMarkSweepGC 可能会有所帮助,因为它将尝试同时执行GC操作(不停止您的程序;详见此处)。

这里有一篇由Til Gene发表的非常好的演讲(Azul Systems的CTO,高性能JVM制造商,并发表了几个GC算法),介绍了JVM中的GC。


5
调整Full GC并不容易。更好的方法是减少垃圾产生。减少垃圾产生可以减轻集合对于将对象传递到年老代的压力,这样做的成本更高。
我建议您使用内存分析器来:
- 减少垃圾的产生。在许多应用程序中,这可以相对容易地减少2-10倍。 - 减小您创建的对象的大小,例如使用原始和较小的数据类型,如double而不是BigDecimal。 - 回收可变对象而不是丢弃它们。 - 如果可能,保留客户端上的较少数据。
通过减少垃圾的产生,对象更有可能在Eden或Survivor空间中死亡,这意味着您会有更少的Full collections,这些集合也可能更短。
不要认为您必须接受大量的集合,极端情况下,您几乎可以完全避免它 http://vanillajava.blogspot.ro/2011/06/how-to-avoid-garbage-collection.html

我同意减少垃圾的创建。此外,如果您在函数内保留所有引用,则它们将在函数退出时被收集。或者应该这样做。 - Zan Lynx
@ZanLynx 是的,它们应该这样做,但我不会假设它们这样做。 ;) 实际上,如果您能找到这种情况可行的例子,请告诉我。逃逸分析会将对象放在堆栈上,而不是堆上,因此不会被收集。 - Peter Lawrey

2

避免调用Runtime.getRuntime().gc() - 当手动触发垃圾收集时,它要么什么也不做,要么会执行全停顿垃圾收集。您应该使用增量垃圾收集。

您尝试过使用jdk安装的服务器jvm吗?它会更改一堆默认配置设置(包括垃圾收集),而且很容易尝试 - 只需在java命令中添加-server。

java -server

都是些什么垃圾被创建出来了?能否减少它的生成量呢?在可能的情况下,尽量使用valueOf方法。通过使用更少的内存,您将节省自己在垃圾回收和内存分配方面的时间。


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