Java IOException:在Linux上发送UDP数据包时没有可用的缓冲区空间

7

我有一个第三方组件,它在某些情况下试图向太多不同的地址发送UDP消息。这是软件启动时发生的突发事件,情况是暂时的。我实际上不确定它是消息数量本身还是每个消息都发送到不同的IP地址。

无论如何,改变底层协议或有问题的组件都不是一种选择,所以我正在寻找解决方法。 StackTrace看起来像这样:

java.io.IOException: No buffer space available
    at java.net.PlainDatagramSocketImpl.send(Native Method)
    at java.net.DatagramSocket.send(DatagramSocket.java:612)

这个问题至少出现在Java版本1.6.0_13和1.6.0_10以及Linux版本Ubuntu 9.04和RHEL 4.6。

是否有任何Java系统属性或Linux配置调整可以帮助解决此问题?

5个回答

10

我终于确定了问题所在。Java IOException 给出的错误信息是“无可用缓冲区”,但根本问题是本地ARP表已满。在Linux上,ARP表查找默认为1024(文件路径:/proc/sys/net/ipv4/neigh/default/gc_thresh1, /proc/sys/net/ipv4/neigh/default/gc_thresh2, /proc/sys/net/ipv4/neigh/default/gc_thresh3)。

我的情况(也可能是你的情况)是,你的Java代码从与目标地址相同子网的IP地址发送UDP数据包。此时,Linux机器会执行ARP查找,将IP地址转换为硬件MAC地址。由于你向许多不同的IP地址发送数据包,本地ARP表很快就填满了,达到1024个条目,这时就会抛出Java异常。

解决方案很简单,要么通过编辑我之前提到的文件来增加限制,要么将服务器移到与目标地址不同的子网中,然后Linux机器就不再执行邻居ARP查找(而是由网络上的路由器处理)。


不是第一个人会去看的地方。你是怎么破解它的? - Thorbjørn Ravn Andersen
@ThorbjørnRavnAndersen:我现在不知道如何查找,但大约8年前,/proc/slabinfo有一个单独的条目用于“neigh”(例如ARP)条目。当你得到ENOBUFS时,只需查看slabinfo以查看那些缓冲区即可。现在它可能已经合并到一些kmalloc-size条目中了。 - ninjalj

3

在发送大量消息时,尤其是在Linux系统下使用千兆以太网时,内核的默认参数通常不是最优的。您可以通过以下方式增加Linux内核网络缓冲区大小:

echo 1048576 > /proc/sys/net/core/wmem_max
echo 1048576 > /proc/sys/net/core/wmem_default
echo 1048576 > /proc/sys/net/core/rmem_max
echo 1048576 > /proc/sys/net/core/rmem_default

作为root用户。 或使用sysctl。
sysctl -w net.core.rmem_max=8388608 

有大量的网络选项可供选择

请参阅IBM的Linux网络调整更多调整信息


@auramo,您使用的是哪个JVM?是来自sun build还是来自您发行版的OpenJDK/JVM?我建议您使用来自您发行版的JVM,如果可能的话使用开放式的JVM会更好,因为它将与内核/ libc 接口更精确而不太"安全"。 - Aiden Bell
我正在使用1.6.0_13和1.6.0_10的Sun版本。虽然我可以轻松尝试OpenJDK版本,但在项目的这个阶段从Sun实现更改为OpenJDK将是一个重大麻烦。 - auramo
如果您切换到基于Sun源码的OpenJDK,则可以解决问题,并且可以在Sun论坛上询问可能导致此问题的差异...并帮助重新配置Sun JRE版本以使其正常工作 ;) 它可能不是Linux内核,而是中间某个组件(例如Sun blob可能静态链接到的c库)。 - Aiden Bell
我有同样的怀疑:Sun的JDK有一些C代码,它会进行库调用,覆盖了我尝试更改的sysctl值。在谷歌搜索时,我遇到了几篇文章,说你可以通过客户端代码中的某些C API调用来覆盖udp_mem、wmem_max等。 - auramo
我相信这是可行的。这就是虚拟机架构的魅力 :p - Aiden Bell
显示剩余2条评论

1
可能有点复杂,但据我所知,Java使用SPI模式来处理网络子库。这使您可以更改用于各种网络操作的实现。如果您使用OpenJDK,则可以获得一些提示,了解如何以及何时使用您的实现进行包装。然后,在您的实现中,例如通过一些休眠来减慢I/O速度。

或者,只是为了好玩,您可以使用修改后的实现覆盖默认的DatagramSocket。将其命名为相同的包名称,并且据我所知,它将优先于默认的JRE类。至少这种方法在某些有缺陷的第三方库上对我有效。

编辑:

1服务提供程序接口是一种在API中分离客户端和服务代码的方法。此分离允许不同的客户端和不同的提供程序实现。通常可以从名称以Impl结尾的名称中识别出来,就像在您的堆栈跟踪中一样,java.net.PlainDatagramSocketImpl是提供程序实现,而DatagramSocket是客户端API。

你说你不想在整个通信过程中减慢速度。有几个技巧可以避免这种情况,例如在代码中测量时间并在第一个传入方法调用开始的1-2分钟内减缓通信。然后您可以跳过睡眠。

另一种选择是在库中识别行为不当的类,将其JAD并进行修复。然后替换库中的原始类文件。


你能告诉我SPI模式是什么吗?我想克服1-2分钟的启动时间突发问题。为此,我绝对不想减慢我的UDP I/O,在应用程序运行期间需要保持高速(它是一个服务器应用程序)。 - auramo

0

我尝试使用 WIFI 连接到数据库在两个本地 JVM 中运行一致性集群时,出现了这个错误。 如果使用以太网运行它,则可以正常运行。


0

我目前也遇到了这个问题,无论是Debian还是RHEL都有。目前我认为已经将其隔离到NIC和/或NIC驱动程序上。您的硬件配置是什么?是否也出现了这个问题?这似乎只发生在我们最近获得的具有Broadcom Corporation NetXtreme II BCM5708千兆以太网NIC的新Dell PowerEdge服务器上。

我也可以确认,它是在短时间内向许多不同IP地址快速生成出站UDP数据包所导致的。我尝试编写了一个简单的Java应用程序来重现它(因为我们的问题出现在snmp4j中)。

编辑

请查看我的答案:Java IOException: No buffer space available while sending UDP packets on Linux


我的问题发生在许多硬件配置上,包括惠普工作站和机架服务器。最终,我们不得不对底层组件进行修改(这个组件是我们公司内另一个团队开发的Java组件),该组件触发了过多的网络消息,导致了问题的出现。现在,这个组件发送的UDP请求/响应大大减少了,我们的问题也得到了解决。 - auramo

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