Netty Memory Leak
Native Memory Leak
Netty Memory Leak
Exchange was deprecated in favor of Retrieve
WebClient 의 exchange 메서드는 Memory Leak 가능성 때문에 deprecated 되었다.
Memory Leak:
Native Memory Leak
Reactor Netty Memory Leak - Toss
Netty 가 요청/응답 바디를 저장할때 Buffer 를 사용하는데, Direct Buffer 와 Heap Buffer 가 있다. Reactor Netty HttpClient 는 기본적으로 Socket I/O 성능에 이점을 가지는 Direct Buffer 를 사용한다.
Why need direct memory?
- Direct memory have one fewer copy operation when compared with heap memory ("file/socket — OS memory — jvm heap" vs "file/socket — direct memory")
- Direct memory could manage its own lifecycle, reducing the pressure on garbage collector.
따라서, Reactor Netty 를 사용하는 프로젝트에서 요청/응답 바디를 올바르게 처리하지 않으면 native memory leak 이 발생할 수 있다.
Netty 는 요청/응답을 담기 위해 ByteBuf 를 사용하는데, ThreadPool 처럼 Buffer Pool 을 구현해서 사용한다.
이때 pool 에서 buffer 를 할당받을때 reference counter 초기 값은 1이고, 동일한 buffer 를 다른 곳에서도 사용하면 retain()
함수를 통해
reference counter 를 증가시킨다. 반대로 buffer 를 사용하지 않을때는 release()
함수를 통해 reference counter 를 감소시킨다.
요청 바디를 캐싱하는 경우, 요청 바디를 가져올 때 reference counter 가 증가했지만, 저장된 바디를 잃어 버리면서 감소 로직이 실행되지 않으면 native memory leak 이 발생할 수 있다.
connection pool 이슈와 memory leak 이슈 모두 잡기:
.filter { request, next ->
val isCancelled = AtomicReference(false)
val responseRef = AtomicReference<ClientResponse?>(null)
next.exchange(request)
.doOnNext(responseRef::set)
.doFinally {
if (isCancelled.get()) {
releaseResponseBody(responseRef.get())
}
}
.cache()
.doOnCancel {
isCancelled.set(true)
releaseResponseBody(responseRef.get())
}
}
Netty 에서 너무 큰 데이터(large data)를 한꺼번에 전송(write)하면 너무 많은 메모리를 사용하거나 OutOfMemory 가 발생 한다고 한다.
ChannelInitializer 구현체에서 initChannel 할때 ChunkedWriteHandler 를 추가하고
pipeline.addLast("ChunkedWriteHandler", new ChunkedWriteHandler());
대량데이터를 Mail Client 에 전송할때 ChunkedStream 을 write 해 주면 ChunkedStream 을 사용하면 데이터 크기를 기본 8192Byte 로 조각 내서 Context 에 write 한다.
InputStream in = new ByteArrayInputStream(XXX);
ctx.write(new ChunkedStream(in));