强制显式删除Java对象

我正在研究处理大量非常密集流量的Java服务器。 服务器接受来自客户端的数据包(通常为几兆字节)并将其转发给其他客户端。 服务器从不显式存储任何传入/传出数据包。 然而,服务器不断遇到OutOfMemoryExceptionexception。

我将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 

查看您的代码:每次数据包到达或发送时是否新创建了ObjectInput/OutputStream实例,如果是,它们是否正确关闭? 如果没有,你会在每次读/写后调用reset()吗? 对象流类保留对它们看到的所有对象的引用(为了避免每次引用时重新发送同一对象),防止它们被垃圾回收。 大约10年前我遇到了这个问题 – 实际上我第一次使用分析器来诊断内存泄漏…

对象流包含对从它们写入/读取的每个对象的引用。 这是因为序列化协议允许对流中较早出现的对象进行反向引用。 您可能仍然可以使用此设计,但使用writeUnshared / readUnshared而不是writeObject / readObject。 我想,但不确定,这会阻止流保持对对象的引用。

正如考恩所说, reset()方法也在这里发挥作用。 最安全的做法是在写入ObjectOutputStream时使用writeUnshared ,然后立即执行reset()

当JVM位于OutOfMemoryError的边缘时,它将运行GC。

所以事先调用System.gc()并不能解决问题。 问题是要在其他地方修复。 基本上有两种方式:

  1. 编写内存高效代码和/或修复代码中的内存泄漏。
  2. 为JVM提供更多内存。

使用Java Profiler可能会提供有关内存使用情况和潜在内存泄漏的大量信息。

更新 :根据您的编辑,有关导致此问题的代码的更多信息,请查看Geoff Reedy在本主题中的答案,该答案建议使用ObjectInputStream#readUnshared()ObjectOutputStream#writeUnshared() 。 (链接)Javadocs也很好地解释了它。

System.gc()仅是对Java虚拟机的推荐。 你调用它,JVM可能会也可能不会运行垃圾回收。

OutOfMemoryException可能由两件事引起。 您保留(不需要的)对象的引用,或者您接受许多数据包。

第一种情况可以通过分析器进行分析,您可以在其中查找仍有多少引用。 记忆韭菜的良好迹象是增加服务器的内存消耗。 如果每个额外的请求都会使您的Java进程增长一点,那么您可能会在某处保留引用(jconsole可能是一个好的开始)

如果您接受的数据超过了您可以处理的数据,则必须阻止其他请求,直到其他请求完成为止。

您不能调用显式垃圾回收。 但这不是问题所在。 也许您正在存储对这些消息的引用。 跟踪它们的处理位置,并确保在使用它们之后没有对象保持对它们的引用。

为了更好地了解最佳实践,请阅读Effective Java,第2章 – 它是关于“创建和销毁对象”

您不能明确强制删除,但是您可以确保仅通过在内存中保留一个直接引用,然后使用Reference对象来保存对它的垃圾可收集引用来保持对消息的引用。

如何使用(小的,有界大小的)队列来处理消息,然后使用辅助SoftReference队列来提供给第一个队列? 这样您就可以保证处理将继续,但如果消息太大(在这种情况下引用队列将被转储),您也不会出现内存错误。

你可以在java中调整垃圾收集,但你不能强迫。

如果你得到了OutOfMemoryexception,那么显然仍然有一些对这些对象的引用。 您可以使用诸如jhat之类的工具来找出这些引用所在的位置。

你需要找出你是否持有超过必要的物体。 第一步是在案例中获取一个分析器并查看堆,看看为什么没有收集对象。

虽然你已经给了JVM 1GB,但是如果很快就会创建大量的对象,那么你的年轻一代可能太小了,迫使他们进入老一代,他们不会被迅速删除。

有关GC调整的一些有用信息: http : //java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html

服务器接受来自客户端的数据包(通常为几兆字节)并将其转发给其他客户端。

您的代码可能在转发之前完全收到“数据包”。 这意味着它需要足够的内存来完全存储所有数据包,直到它们被完全转发,并且当这些数据包“很多兆字节”时,这意味着你确实需要大量内存。 它还会导致不必要的延迟。

你可能也有内存泄漏,但如果上述情况属实,那么这种“存储转发”设计是你最大的问题。 如果您重新设计应用程序以完全不接收数据包,而是直接将它们直接传输到客户端,即一次只读取包的一小部分并立即将其传输给客户端,您可以将内存使用量减少95%。 以与存储转发相同的方式对客户端执行此操作并不困难。

手动触发System.gc不是一个好的答案,正如其他人在这里发布的那样。 它不能保证运行,并且它会触发一个完整的gc,这可能会使你的服务器在运行时长时间停机(如果你给你的服务器一个GB的RAM,> 1秒,我已经看过几分钟在较大的系统上长时间暂停)。 你可以调整你的gc肯定有帮助,但不能完全解决问题。

如果您正在从一个流中读取对象,然后将它们写入另一个流,那么您可以将整个对象保存在内存中。 如果您声明这些对象很大,那么这可能就是您的问题。 尝试重写你的IO,这样你就可以从1个流中读取字节并将它们写入另一个流,而无需显式保存完整的对象(尽管如果你需要validation/validation对象,我无法看到它如何与对象序列化/反序列化一起工作)。

只是添加到所有以前的回复:System.gc()不是JVM启动垃圾收集的命令..这是一个温顺的方向,并不保证任何事情发生。 JVM规范将它留给供应商来调用gc调用需要做的事情。 供应商甚至可以选择什么都不做!

你提到你明确需要整个收到的数据包才能发送它? 嗯,这并不意味着你需要将它全部存储在内存中,是吗? 是否可以将收到的数据包保存到外部存储(即使SSD太慢也可能是ram-disk或DB),然后将它们直接传送给收件人,而无需将它们完全加载到内存中?

如果您的服务器在它死亡之前运行至少几分钟,您可能想尝试在Visual VM中运行它。 您至少可以更好地了解堆的增长速度以及堆中的对象类型。

Interesting Posts