WildFly 12+ 中的 Linux 文件句柄(管道)泄漏

WildFly 12+ 中的 Linux 文件句柄(管道)泄漏

我试图确定我观察到的行为是否正确或者 WildFly 是否正在泄漏文件句柄描述符。

在从 WildFly 11 升级到 14 后进行标准性能测试时,我们遇到了打开文件过多的问题。深入研究后发现,实际上是 WildFly 打开的管道数量在增加。

为了帮助重现该问题,我创建了一个包含大图像(100mb,以简化测试)的简单 JSF 2.2 应用程序。我使用标准 JSF 资源 URL 检索图像:

/contextroot/javax.faces.resource/css/images/big-image.png.xhtml

并且还尝试添加 omnifaces 并使用未映射的资源处理程序 URL:

/contextroot/javax.faces.resource/css/images/big-image.png

添加 Omnifaces 并没有改变我所看到的行为,我只是将它包括在内,因为我们最初认为它可能是一个促成因素。

我看到的行为:
WildFly 启动后,jstack 报告有两个线程匹配,default task-*这是默认值task-core-threads

如果我对大图像发送 5 个并发请求,default task-*则会产生 3 个新线程来处理这些请求。还将创建 3 个新的 Linux 管道。

如果我停止请求并等待 2 分钟(默认值task-keepalive),则 3 个线程将被删除。管道保持打开状态。

定期 - 我相信大约每隔 4.5 分钟就会进行某种清理,并且会移除上述步骤中遗留的管道。

但是...如果删除了原来的两个工作线程中的一个,例如删除了任务 1、任务 3 和任务 4,剩下任务 2 和任务 5,则与任务 1 关联的管道永远不会被清理。

随着时间的推移,这些管道逐渐增多,据我所知,它们从未被移除过。这是某个地方的泄漏吗?如果是,是哪里?JSF?WildFly?Undertow?

我尝试过的事情:
WildFly 14、17 和 18
带有和不带有 Omnifaces(2.7 和 3.3)
将最小和最大线程更改为相同 - 这可以防止句柄累积,但我不想走这条路

答案1

我也遇到了这种泄漏。两个管道和一个 epoll 选择器的三元组中的句柄“丢失”了。(@Gareth:你能确认这一点吗?查看 /proc/$PID/fd 中的管道和匿名 inode)。由此看来,它似乎是由 Java NIO 通道产生的。

我发现,通过调用完整 GC 可以释放句柄(至少如此) (@Gareth:您能确认这一点吗)?我使用的是经过精心调优的 Java8-JVM,启用了 G1GC,令人欣慰的是,完整 GC 很少发生。但作为负面后果,它会同时消耗数千个 FH 三元组。

由于句柄是可释放的,所以它不是真正的泄漏,而是软/弱/幻影引用的效果。

上周已经两次达到指定的操作系统限制(带有 Wildfly 的 JVM 在 LX-Container 内运行)。因此,作为生产的第一个解决方法,我编写了一个看门狗,如果管道句柄级别达到限制,它会使用 jcmd 调用 FGC。

这是在运行大约 20 多个应用程序的 Wildfly-13(平衡)对上观察到的。它似乎与具体应用程序无关,因为如果我禁用单个应用程序的负载平衡(在其中一个对上),它也会发生(在两个 Wildfly 上)。

它不会“显示”在我们的其他 Wildflies(对)上,但有另一组具有其他用例的应用程序。内存循环更多,堆上的“压力”也更大。也许这会以另一种方式触发持有文件句柄的对象的同步释放。

通过使用“内存分析工具”查看堆转储,我发现 和 的实例数量相当高,并且sun.nio.ch.EPollArrayWrappersun.nio.ch.EPollSelectorImpl的入站引用相等org.xnio.nio.NioXnio$FinalizableSelectorHolder

答案2

将 WildFly 运行时从 Java 8 迁移到 11 后开始遇到此问题。

Java 11 将默认 GC 算法更改为G1。 在G1老生代对象收集非常有选择性,并且只有在超过某个堆占用阈值后才会收集。如果只有少数对象被提升到老生代,并且有助于达到此阈值,则可能会在org.xnio.nio.NioXnio$FinalizableSelectorHolder那里堆积很长时间,保留打开的文件描述符。

在我的例子中,将垃圾收集切换到使用并发标记和清除解决了这个问题,尽管我相当确定 G1 可以进行调整以更积极地收集老一代。-XX:-G1UseAdaptiveIHOP并且-XX:InitiatingHeapOccupancyPercent切换播放–XX:NewRatio。另一种方法实际上可能是减少老生代堆( )或整个堆的大小。

相关内容