我正在处理一个处理大量非常密集流量的Java服务器。服务器接受来自客户端的数据包(通常是几兆字节)并将它们转发给其他客户端。服务器从不显式存储任何传入/传出数据包。然而,服务器不断遇到OutOfMemoryException
例外。
我将System.gc()
添加到服务器的消息传递组件中,希望释放内存。另外,我将JVM的堆大小设置为千兆字节。我仍然得到了许多例外。
所以我的问题是:如何确保兆字节消息无限期排队(尽管不需要)?有没有办法让我在这些对象上调用“delete”来保证它们不使用我的堆空间?
try
{
while (true)
{
int r = generator.nextInt(100);//generate a random number between 0 and 100
Object o =readFromServer.readObject();
sum++;
// if the random number is larger than the drop rate, send the object to client, else
//it will be dropped
if (r > dropRate)
{
writeToClient.writeObject(o);
writeToClient.flush();
numOfSend++;
System.out.printf("No. %d send\n",sum);
}//if
}//while
}//try
答案 0 :(得分:19)
对象流保存对从它们写入/读取的每个对象的引用。这是因为序列化协议允许对流中较早出现的对象进行反向引用。您可能仍然可以使用此设计,但使用writeUnshared / readUnshared而不是writeObject / readObject。我想,但不确定,这会阻止流保持对对象的引用。
正如考恩所说,reset()
方法也在这里发挥作用。最安全的做法是在写入writeUnshared
reset()
后紧跟ObjectOutputStream
答案 1 :(得分:11)
当JVM位于OutOfMemoryError
边缘时,将运行GC。
因此事先致电System.gc()
并不能解决问题。问题是要在其他地方修复。基本上有两种方式:
使用Java Profiler可能会提供有关内存使用情况和潜在内存泄漏的大量信息。
更新:根据您的修改,有关导致此问题的代码的详细信息,请查看建议使用Geoff Reedy's answer in this topic和ObjectInputStream#readUnshared()
的ObjectOutputStream#writeUnshared()
代替。 (链接)Javadocs也很好地解释了它。
答案 2 :(得分:4)
System.gc()仅是对Java虚拟机的推荐。您调用它,JVM可能会也可能不会运行垃圾回收。
OutOfMemoryException可能由两件事引起。您保留(不需要的)对象的引用,或者您接受许多数据包。
第一种情况可以通过分析器进行分析,您可以在其中查找仍有多少引用。记忆韭菜的良好迹象是增加服务器的内存消耗。如果每个额外的请求都会使您的Java进程增长一点,那么您可能会在某处保留引用(jconsole可能是一个好的开始)
如果您接受的数据超出了可以处理的数量,则必须阻止其他请求,直到其他请求完成为止。
答案 3 :(得分:3)
您无法调用显式垃圾回收。但这不是问题所在。也许您正在存储对这些消息的引用。跟踪它们的处理位置,并确保在使用它们之后没有对象保持对它们的引用。
为了更好地了解最佳做法,请阅读Effective Java, chapter 2 - 它是关于“创建和销毁对象”
答案 4 :(得分:3)
查看您的代码:每次数据包到达或发送时,新创建的ObjectInput/OutputStream
个实例是否正确,如果是,它们是否正确关闭?如果没有,您是否在每次读/写后调用reset()
?对象流类保留对它们看到的所有对象的引用(为了避免每次引用时重新发送同一对象),防止它们被垃圾回收。大约10年前我遇到了这个问题 - 实际上我第一次使用分析器来诊断内存泄漏......
答案 5 :(得分:2)
您不能明确强制删除,但是您可以确保仅通过在内存中保留一个直接引用,然后使用Reference对象来保存对它的垃圾可收集引用来保留对消息的引用。
如何使用(小的,有界大小的)队列来处理消息,然后使用辅助SoftReference队列来提供给第一个队列?通过这种方式,您可以保证处理将继续,但如果消息太大(在这种情况下参考队列将被转储),您也不会出现内存错误。
答案 6 :(得分:2)
你可以在java中tune垃圾收集,但你不能强迫。
答案 7 :(得分:1)
如果你得到了OutOfMemory异常,那么显然仍然会有一些对这些对象的引用。您可以使用jhat之类的工具来查找这些引用所在的位置。
答案 8 :(得分:1)
您需要确定是否持有超过必要时间的物体。第一步是在案例中获取一个分析器并查看堆,看看为什么没有收集对象。
虽然你已经给了1GB的JVM,但是如果很快就会创建大量的对象,那么你的年轻一代可能太小了,迫使他们进入老一代,不会很快被删除。
有关GC调整的一些有用信息: http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html
答案 9 :(得分:1)
服务器接受来自的数据包 客户端(通常是几兆字节)和 将它们转发给其他客户。
您的代码可能在转发之前完全收到“数据包”。这意味着它需要足够的内存来完全存储所有数据包,直到它们被完全转发,并且当这些数据包“很多兆字节”时,这意味着你确实需要大量内存。它还会导致不必要的延迟。
你可能也有内存泄漏,但如果上述情况属实,那么这种“存储转发”设计是你最大的问题。如果您重新设计应用程序以完全不接收数据包,而是直接将它们直接传输到客户端,即一次只读取数据包的一小部分并立即将其传输给客户端,则可以将内存使用量减少95%。以与存储转发相同的方式对客户端进行完全相同的操作并不困难。
答案 10 :(得分:0)
手动触发System.gc不是一个好的答案,正如其他人在此发布的那样。它不能保证运行,并且它会触发一个完整的gc,它可能会在运行时长时间挂起你的服务器(如果你给你的服务器提供GB的RAM,则大约1秒,我看过几个 - 大型系统上的长时间暂停)。你可以调整你的gc,这肯定会有所帮助,但不能完全解决问题。
如果您正在从一个流中读取对象,然后将它们写入另一个流,那么您可以将整个对象保存在内存中。如果您声明这些对象很大,那么这可能是您的问题。尝试重写IO,以便从1个流中读取字节并将它们写入另一个流而无需显式保存完整对象(尽管如果您需要验证/验证对象,我无法看到它如何与对象序列化/反序列化一起工作)。
答案 11 :(得分:0)
只是添加到所有以前的回复:System.gc()不是JVM启动垃圾收集的命令..这是一个温顺的方向,并不保证任何事情发生。 JVM规范将它留给供应商来调用gc调用需要做的事情。供应商甚至可以选择什么都不做!
答案 12 :(得分:0)
你提到你明确需要整个收到的数据包才能发送它?嗯,这并不意味着你需要将它全部存储在内存中,是吗?是否可以将收到的数据包保存到外部存储(如果SSD速度太慢,可能是ram-disk或DB),然后将它们直接传送给收件人而不将它们完全加载到内存中,这是一种可行的体系结构更改吗?
答案 13 :(得分:0)
如果您的服务器在运行之前至少运行了几分钟,您可能希望尝试在Visual VM中运行它。您至少可以更好地了解堆的增长速度,以及堆中的对象类型。