天天看點

Spring Cloud Gateway Direct Memory溢出, diagnosis與解決方案

作者:程式咖大姚
Spring Cloud Gateway Direct Memory溢出, diagnosis與解決方案

一、錯誤描述

io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 16777216 byte(s) of direct memory (used: 939524103, max: 954728448)
        at io.netty.util.internal.PlatformDependent.incrementMemoryCounter(PlatformDependent.java:802)
        at io.netty.util.internal.PlatformDependent.allocateDirectNoCleaner(PlatformDependent.java:731)
        at io.netty.buffer.PoolArena$DirectArena.allocateDirect(PoolArena.java:648)
        at io.netty.buffer.PoolArena$DirectArena.newChunk(PoolArena.java:623)
        at io.netty.buffer.PoolArena.allocateNormal(PoolArena.java:202)
        at io.netty.buffer.PoolArena.tcacheAllocateNormal(PoolArena.java:186)
        at io.netty.buffer.PoolArena.allocate(PoolArena.java:136)
        at io.netty.buffer.PoolArena.allocate(PoolArena.java:126)
        at io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:394)
        at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:188)
        at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:179)
        at io.netty.buffer.AbstractByteBufAllocator.buffer(AbstractByteBufAllocator.java:116)
        at org.springframework.core.io.buffer.NettyDataBufferFactory.allocateBuffer(NettyDataBufferFactory.java:71)
        at org.springframework.core.io.buffer.NettyDataBufferFactory.allocateBuffer(NettyDataBufferFactory.java:39)
        at org.springframework.core.codec.CharSequenceEncoder.encodeValue(CharSequenceEncoder.java:91)
        at org.springframework.core.codec.CharSequenceEncoder.lambda$encode$0(CharSequenceEncoder.java:75)
        at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:113)
                 

二、環境配置

• springboot 2.6.4

• springcloud 2021.0.1

• jdk 1.8

三、原因分析

3.1 Java記憶體區域

Java記憶體主要分為堆區和非堆區:

• 堆區:新生代和老年代,用于存儲對象執行個體

• 非堆區:方法區、直接記憶體、本地方法棧等,方法區用于存儲類資訊,直接記憶體用于NIO

Spring Cloud Gateway Direct Memory溢出, diagnosis與解決方案

3.2 直接記憶體

直接記憶體不是JVM運作時資料區的一部分,用于NIO操作。它通過DirectByteBuffer對象進行管理,避免在Java堆和Native堆中複制資料,提高性能。

直接記憶體的大小不受Java堆限制,但受限于實體記憶體和處理器尋址空間。如果各個記憶體區域的總和大于實體記憶體限制,會導緻OutOfMemoryError異常。

3.3錯誤原因

Spring WebFlux中大量使用了直接記憶體及Netty操作。在兩個地方DirectByteBuffer未正确釋放:

(1) 未捕獲異常,MyCachedBodyOutputMessage中的DirectByteBuffer未釋放。修複方式:

java
MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers);  
return bodyInserter.insert(outputMessage, new BodyInserterContext())
   .then(Mono.defer(() -> {  
       //...  
   }))
   .onErrorResume((Function<Throwable, Mono<Void>>) throwable -> release(exchange, outputMessage, throwable));
           

(2) 使用DataBufferUtils.release()無法釋放DefaultDataBuffer的直接記憶體。修複方式:

DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);  
DataBuffer join = dataBufferFactory.join(dataBuffers);  
//...  
DataBufferUtils.release(join);             

四、總結

通過對Java記憶體區域的了解,查找直接記憶體的使用場景,corrected其釋放問題,解決了直接記憶體溢出的錯誤。我們在程式設計過程中,需要警惕各記憶體區域的使用和監控,特别是直接記憶體,以避免此類問題的發生。希望能起到梳理思路與知識點的作用。歡迎檢閱,不吝賜教。