当前位置:Java -> Java NIO:OutOfMemoryError
Java NIO(New Input/Output)是一个高性能的网络和文件处理API,使您可以进行非阻塞IO。非阻塞IO提供以下优势:
我们的一款应用程序正在利用NIO,但是在Java 11中运行时频繁遇到
‘java.lang.OutOfMemoryError: Direct buffer memory’
。然而,当我们升级到Java 17时,‘java.lang.OutOfMemoryError: Direct buffer memory’
发生的频率大大降低。在本文中,我们想分享我们的发现和解决此问题的方法。
为了证明我们的案例,我们构建了一个简单的Spring Boot应用程序,用于异步上传图片。该应用程序利用Spring WebClient来连接REST API。Spring WebClient底层使用Java NIO技术来处理连接。以下是该应用程序的源代码。
public void webHeavyClientCall(Integer id,String url, String imagePath) {
// Create a WebClient instance
WebClient webClient = WebClient.create();
// Prepare the image file
File imageFile = new File(imagePath);
// Perform the POST request with the image as a part of the request body
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new FileSystemResource(imageFile));
System.out.println("Starting to post an image for Id"+id);
webClient.post().uri(url).contentType(MediaType.MULTIPART_FORM_DATA).body(BodyInserters.fromMultipartData
(body))
.retrieve().bodyToMono(String.class).subscribe(response -> {
System.out.println("Response Id"+id+ ":" + response);
});
}
我们在Java 11中执行了上述代码。经过大约15次迭代后,这个简单的应用程序开始抛出‘java.lang.OutOfMemoryError: Direct buffer memory’
。以下是控制台中打印的输出。
Starting to post an image for Id0
Starting to post an image for Id1
Starting to post an image for Id2
Starting to post an image for Id3
Starting to post an image for Id4
Starting to post an image for Id5
Starting to post an image for Id6
Starting to post an image for Id7
Starting to post an image for Id8
Starting to post an image for Id9
Starting to post an image for Id10
Starting to post an image for Id11
Starting to post an image for Id12
Starting to post an image for Id13
Starting to post an image for Id14
2023-12-06 17:21:46.730 WARN 13804 --- [tor-http-nio-12] io.netty.util.concurrent.DefaultPromise : An
exception was thrown by reactor.ipc.netty.FutureMono$FutureSubscription.operationComplete()
reactor.core.Exceptions$ErrorCallbackNotImplemented:
io.netty.channel.socket.ChannelOutputShutdownException: Channel output shutdown
Caused by: java.lang.OutOfMemoryError: Direct buffer memory
at java.base/java.nio.Bits.reserveMemory(Bits.java:175) ~[na:na]
at java.base/java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:118) ~[na:na]
at java.base/java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:318) ~[na:na]
at java.base/sun.nio.ch.Util.getTemporaryDirectBuffer(Util.java:242) ~[na:na]
at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:164) ~[na:na]
at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:130) ~[na:na]
at java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:496) ~[na:na]
at io.netty.channel.socket.nio.NioSocketChannel.doWrite(NioSocketChannel.java:418) ~[netty-
transport-4.1.23.Final.jar!/:4.1.23.Final]
at io.netty.channel.AbstractChannel$AbstractUnsafe.flush0(AbstractChannel.java:934) ~[netty-
transport-4.1.23.Final.jar!/:4.1.23.Final]
... 18 common frames omitted
我们在Java 17中执行了同样的程序。但是,为了在Java 17中运行这个程序,我们需要进行一些小的修改。以下是在Java 17上运行模拟上述行为的修改后的代码。
public void webHeavyClientCall(Integer id,String url, String imagePath) {
// Create a WebClient instance
WebClient webClient = WebClient.create();
// Prepare the image file
File imageFile = new File(imagePath);
// Perform the POST request with the image as a part of the request body
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new FileSystemResource(imageFile));
System.out.println("Starting to post an image for Id"+id);
webClient.post().uri(url).contentType(MediaType.MULTIPART_FORM_DATA).body(BodyInserters.fromMultipartData
(body))
.retrieve().bodyToMono(String.class).subscribe(response -> {
System.out.println("Response Id"+id+ ":" + response);
});
}
}
升级后,内存使用有所改善。Java 17能够处理的NIO连接至少是Java 11的两倍。以下是控制台中的输出。您可以看到该应用程序能够进行50次连接迭代,之后才会出现‘java.lang.OutOfMemoryError’
。另一方面,Java 11在15次连接后就失败了‘java.lang.OutOfMemoryError’
。
Starting to post an image for Id38
Starting to post an image for Id39
Starting to post an image for Id40
Starting to post an image for Id41
Starting to post an image for Id42
Starting to post an image for Id43
Starting to post an image for Id44
Starting to post an image for Id45
Starting to post an image for Id46
Starting to post an image for Id47
Starting to post an image for Id48
Starting to post an image for Id49
2023-12-12 14:49:38.421 WARN 59559 --- [ctor-http-nio-4] r.netty.http.client.HttpClientConnect :
[bfc8b2c8, L:/127.0.0.1:57435 ! R:localhost/127.0.0.1:8090] The connection observed an error
reactor.netty.ReactorNetty$InternalNettyException: java.lang.OutOfMemoryError: Cannot reserve 4096 bytes
of direct buffer memory (allocated: 202956, limit: 204800)
Caused by: java.lang.OutOfMemoryError: Cannot reserve 4096 bytes of direct buffer memory (allocated:
202956, limit: 204800)
at java.base/java.nio.Bits.reserveMemory(Bits.java:178) ~[na:na]
at java.base/java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:121) ~[na:na]
at java.base/java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:332) ~[na:na]
at io.netty.buffer.UnpooledDirectByteBuf.allocateDirect(UnpooledDirectByteBuf.java:104) ~[netty-
buffer-4.1.73.Final.jar!/:4.1.73.Final]
at io.netty.buffer.UnpooledDirectByteBuf.<init>(UnpooledDirectByteBuf.java:64) ~[netty-buffer
-4.1.73.Final.jar!/:4.1.73.Final]
at io.netty.buffer.UnpooledUnsafeDirectByteBuf.<init>(UnpooledUnsafeDirectByteBuf.java:41) ~
[netty-buffer-4.1.73.Final.jar!/:4.1.73.Final]
at io.netty.buffer.UnsafeByteBufUtil.newUnsafeDirectByteBuf(UnsafeByteBufUtil.java:634) ~[netty-
buffer-4.1.73.Final.jar!/:4.1.73.Final]
at io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:397) ~
[netty-buffer-4.1.73.Final.jar!/:4.1.73.Final]
at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:188) ~
[netty-buffer-4.1.73.Final.jar!/:4.1.73.Final]
at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:179) ~
[netty-buffer-4.1.73.Final.jar!/:4.1.73.Final]
at io.netty.buffer.AbstractByteBufAllocator.buffer(AbstractByteBufAllocator.java:116) ~[netty-
buffer-4.1.73.Final.jar!/:4.1.73.Final]
at org.springframework.core.io.buffer.NettyDataBufferFactory.allocateBuffer(NettyDataBufferFactory.java:71)
~[spring-core-5.3.15.jar!/:5.3.15]
at org.springframework.core.io.buffer.DataBufferUtils$ReadCompletionHandler.request(DataBufferUtils.java:945
) ~[spring-core-5.3.15.jar!/:5.3.15]
‘OutOfMemoryError: Direct buffer memory’
为了解决这个问题,我们使用了yCrash监控工具。该工具能够在生产环境中预测故障发生前的情况。一旦它在环境中预测到故障,它会捕获环境中的360°故障排除工件,分析它们,并立即生成根本原因分析报告。它捕获的工件包括垃圾回收日志、线程转储、堆替代、netstat、vmstat、iostat、top、top -H、dmesg、内核参数和磁盘使用情况…
您可以在这里注册并开始使用该工具的免费版。
yCrash服务器分析了示例应用程序并提供了明确的问题指示和建议。以下是yCrash为SpringBoot WebClient应用程序生成的事故摘要报告。您可以注意到,yCrash清楚地指出了错误,并提出了必要的建议来纠正问题。
yCrash的垃圾回收(GC)分析报告显示,连续运行了Full GCs(请参见下面的截图)。当GC运行时,整个应用程序暂停,不会处理任何事务。整个应用程序都将变得无响应。在SpringBoot WebClient应用程序崩溃并出现OutOfMemoryError
之前,我们观察到了无响应的行为。
Outofmemoryerror
:直接缓冲器内存yCrash的应用程序日志分析报告显示,应用程序遭受了'java.lang.OutOfMemoryError: Direct buffer memory'
(请参见下面的截图),导致了应用程序崩溃。
java.lang.OutOfMemoryError: Direct buffer memory
Outofmemoryerror
?Java NIO对象存储在JVM本地内存的“直接缓冲区内存”区域。(注:JVM中存在不同的内存区域。要了解它们,您可以观看此视频片段)。当我们执行上述两个程序时,我们设置了直接缓冲区内存大小为200k(即,XX:MaxDirectMemorySize=200k
)。在分配了200k直接缓冲区内存的情况下,Java 11只能执行15次迭代,而Java 17可以执行50次迭代。这清楚地表明了JDK团队在Java 17版本中所做的优化。
Xx:Maxdirectmemorysize
因此,如果您的应用程序正在利用Java NIO并在Java 11或更低版本上运行,并且遇到‘java.lang.OutOfMemoryError: Direct buffer memory’
,那么您面前有几种解决方案:
由于升级到Java 17需要更多依赖项,我们使用JVM参数-XX:MaxDirectMemorySize=1000k
将直接内存大小增加到更高的值。做出这一更改后,应用程序的Java 11版本能够成功运行而不出现任何错误。
在本文中,我们讨论了Java 11中由Java NIO引起的‘java.lang.OutOfMemoryError: Direct buffer memory’
以及解决这个问题的潜在解决方案。希望对您有所帮助。
推荐阅读: 百度面经(6)
本文链接: Java NIO:OutOfMemoryError