使用java 11编译后的堆外泄漏

使用java 11编译后的堆外泄漏,java,memory,memory-leaks,heap,java-11,Java,Memory,Memory Leaks,Heap,Java 11,将SpringWeb应用程序maven build升级到Java11后,我们看到Java进程的内存消耗不断增加 工作正常:使用Java8JDK+构建,在使用Java11的服务器上运行 存在漏洞:使用Java 11构建+在使用Java 11的服务器上运行 在堆转储中甚至本机内存跟踪中都看不到泄漏,该进程一直在增加,直到物理内存+交换已满且该进程被系统终止。什么样的问题可能会导致这种问题?在Java 11中,ForkJoinPool类的行为略有不同 自上次使用以来,线程终止之前的默认运行时间为60秒

将SpringWeb应用程序maven build升级到Java11后,我们看到Java进程的内存消耗不断增加

工作正常:使用Java8JDK+构建,在使用Java11的服务器上运行

存在漏洞:使用Java 11构建+在使用Java 11的服务器上运行


在堆转储中甚至本机内存跟踪中都看不到泄漏,该进程一直在增加,直到物理内存+交换已满且该进程被系统终止。什么样的问题可能会导致这种问题?

在Java 11中,ForkJoinPool类的行为略有不同

自上次使用以来,线程终止之前的默认运行时间为60秒。在Java8中,这是未记录的,但实际上是用2秒硬编码的。对于超大的池,Java8实现在创建池两秒钟后终止空闲线程。但是Java 9/11版本的类使它们保持了几分钟的活动

比较线程的数量和生存期。由于在应用程序启动或创建ForkJoinPools对象时,未使用的线程可能不再提前终止,因此延长线程的生命周期很容易导致内存问题

有关类似问题,请参见以下问题:


在Java9中,引入了一个配置值的函数。要获得与Java 8编译相同的行为,必须在编译到Java 9之前将keepAliveTime显式设置为2秒或减小ForkJoinPool对象的大小。

在深入研究Java的编译器代码后,我发现了一个有趣的变化,它是在Java 9中引入的,我还不知道。此更改可能导致不同的行为,具体取决于编译目标:

众所周知,大多数优化都是由JIT编译器而不是javac完成的,而javac编译器仍然会进行一些代码优化。在Java9之前,这些优化之一是将字符串连接转换为StringBuilder::append链。从Java 9开始,javac使用invokedynamic调用新引入的Java.lang.invoke.StringConcatFactory类,而不是转换为StringBuilder:append调用。因此,当您编译到Java8时,javac将生成优化的字节码,而当您编译到Java9时,优化将在运行时委托给上述内置类


相应的文档提供了有关此更改的更多详细信息。JEP280的一个成功指标是字符串连接性能不能倒退。但已经报告了潜在的性能下降。根据bug条目,编译为Java8的字符串连接代码在Java11u上的性能似乎比编译为Java9或Java11的相同代码更好。错误条目仍未解决,因此性能可能不是这里唯一的回归

很抱歉这么问,但是您已经检查过,不是元空间无限期地增长并最终导致JVM被系统杀死的吗?元空间保存着加载的类,它不是堆的一部分。@tquadrat是的,我们有NewRelic监控这些统计数据,我们没有看到异常值。你有没有在没有监控的情况下尝试过,如果你能很容易地摆脱这些东西?@tquadrat是的,我们了解到java代理的使用不能很容易地跟踪,因此我们删除了启动标志,但没有任何更改。你能提供关于内存消耗不断增加的更多信息吗?JVM跟踪实用程序的任何相关报告?这就像是在黑暗中猜测,没有任何附加信息……。为什么这要取决于代码是用Java 8还是Java 11编译的?对不起,我完全没有注意到Java 8构建也是在使用Java 11的服务器上执行的,而不是在Java 8上执行的。因此,ForkJoinPool中改变的行为无法解释为什么Java 8构建在Java 11上运行良好。无论使用什么Java构建,ForkJoinPool的行为都发生了改变。因此,我仍然建议仔细查看线程的数量。这可能是奇怪的内存泄漏的原因。也许这两个版本之间还有其他差异(例如,由于第三方库中的多个发布JAR),也许这两个版本的组合造成了差异。嗯,有趣的一点是,从升级之前开始,我们的New Relic统计数据没有显示线程数量增加,而且我们没有使用ForkJoinPool(但我想可能存在依赖关系)。