回收 DatagramPacket 对象安全吗?

Is it safe to recycle DatagramPacket objects?

问题:

假设您要传输多达 10 个 MB/s,回收 DatagramPacket 个对象而不是每次发送数据包都创建一个新对象是个好主意吗?


故事:

我正在创建一个 LAN 文件同步应用程序,有时处理超过 30 GB 的文件。文件同步应用程序将通过 100Mbit 有线局域网传输文件。我已经有一个防丢包系统(可以完美运行)。

该程序运行良好,但占用了大约 10% CPU 的使用率,并且由于这是一个后台应用程序,这对我来说太多了。理想情况下,它将达到 3% 左右。

在进行性能分析时,我发现垃圾收集器正在运行,每隔几秒就会激活一次。我知道对象创建(当大量完成时)对于 Java 来说变得很繁重,所以现在我正在尝试回收尽可能多的对象和数组。每个包含文件数据的数据包的大小为 1450 字节,这意味着以 10 MB/s 的速度传输大约每秒 7,200 个数据包。我决定开始回收这些数据包(即发送数据包时,DatagramPacket 对象将添加到列表中,5 秒后 DatagramPacket 可以重新使用)。当一个DatagramPacket被重新使用时,方法DatagramPacket.setData()用于分配它要发送的数据。

除了发送包含文件数据的数据包外,我还大约每秒发送一次小数据包来尝试确定连接的 ping。这些 ping 数据包的大小为 10 个字节。


错误:

在使用 DatagramPacket 回收功能测试我的应用程序大约 30 分钟后,奇怪的错误开始弹出。有一次正在传输的文件损坏了,而其他时候我得到了一些我无法理解的东西……下面是我的 class 的一些代码。 整数length只能通过applyData()方法设置。

public class PacketToSend {

    private int length;
    private DatagramPacket packet;

    ...

    public void applyData(byte[] newData) {
        try {
            length = newData.length;
            packet.setData(newData, 0, length);
        } catch(java.lang.IllegalArgumentException e) {
            System.out.println("Arraylength = "+newData.length);
            System.out.println("length value = "+length);
        }
    }

    ...

}

每次测试大约20-40分钟后,我得到一个IllegalArgumentException,告诉我newData的大小是10,length的值是1450,因此说长度是非法的。这怎么可能?变量 length 没有在其他任何地方修改,但在此方法中,并且在调用 setData() 之前设置!就好像 DatagramPacket 随机切换到发送 ping 数据...

这些错误仅在我启用 DatagramPacket 回收功能时发生。

请注意,发送数据包后,它会被放在一个列表中,等待 5 秒后才能再次使用。我想知道 OS 是否以某种方式影响了这些数据包,或者某些本机代码可能正在操纵数据。

我的程序中只有一个线程发送数据包,所以这不是线程或同步问题。

因此我的问题是:回收 DatagramPacket 对象而不是每次发送数据包时都创建一个新对象是个好主意吗?还是我在玩火和我真的应该不管的东西?


尝试的修复:

Is it safe to recycle DatagramPacket objects?

据我所知或可以确定,重用 DatagramPacket 个实例本身并没有不安全的地方。

另一方面,只有在两个或多个线程之间共享实例时,您描述的错误才有意义,并且在没有适当同步的情况下从多个线程访问共享对象绝对是不安全的。没有多少等待可以替代同步,因此在重用之前强加 5 秒延迟的策略可能会适得其反——它不能保证正确操作,但它可能会导致您的程序维护比实际需要更多的活动对象。

没有您的程序架构的详细信息,我们只能笼统地谈论您可以采取哪些措施来解决这种情况。在最一般的层面上,备选方案是避免在线程之间共享对象并以线程安全的方式访问共享对象。然而,任何线程安全的对象共享机制都会带来相当大的开销,我倾向于认为在一次文件传输过程中执行 2000 万次线程安全操作的成本太高,无法接受。因此,最好的办法是避免共享对象。

根据需要创建新的 DatagramPackets 并且不允许它们脱离创建它们的线程是实现这一目标的一种方法。由于这对您造成过多的 GC,下一个合乎逻辑的步骤可能是维护 per-thread 可重用数据包队列。您可以为此使用 ThreadLocal,但如果每个文件传输都由单个线程管理,那么您也可以考虑使用每个文件队列。无论哪种方式,也要注意其他共享,例如 DatagramPackets 携带的数据缓冲区数组(这很可能是您可以重用的其他东西)。

此外,如果您注意不要在线程之间共享数据,那么您应该能够在没有重用延迟的情况下这样做。实际上,每个线程可能不需要超过一个 DatagramPacket 和一个缓冲区数组。这可以使您的代码不仅更高效而且更简单。