22
33## Netty 中的 ByteBuf 为什么会发生内存泄漏
44
5- 在 Netty 中,ByetBuf 并不是只采用可达性分析来对 ByteBuf 底层的 byte[ ] 数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。
6- 在每个 ByteBuf 中都维护着一个 refCnt 用来对 ByteBuf 的被引用数进行记录,当 ByteBuf 的 retain()方法被调用时,将会增加 refCnt 的计数,而其 release()方法被调用时将会减少其被引用数计数。
5+ 在 Netty 中,ByetBuf 并不是只采用可达性分析来对 ByteBuf 底层的 ` byte[] ` 数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。
6+
7+ 在每个 ByteBuf 中都维护着一个 refCnt 用来对 ByteBuf 的被引用数进行记录,当 ByteBuf 的 ` retain() ` 方法被调用时,将会增加 refCnt 的计数,而其 ` release() ` 方法被调用时将会减少其被引用数计数。
78
89``` java
910private boolean release0(int decrement) {
@@ -23,9 +24,11 @@ private boolean release0(int decrement) {
2324}
2425```
2526
26- 当调用了 ByteBuf 的 release()方法的时候,最后在上方的 release0()方法中将会为 ByteBuf 的引用计数减一,当引用计数归于 0 的时候,将会调用 deallocate()方法对其对应的底层存储数组进行释放(在池化的 ByteBuf 中,在 deallocate()方法里会把该 ByteBuf 的 byte[ ] 回收到底层内存池中,以确保 byte[ ] 可以重复利用)。
27- 由于 Netty 中的 ByteBuf 并不是随着申请之后会马上使其引用计数归 0 而进行释放,往往在这两个操作之间还有许多操作,如果在这其中如果发生异常抛出导致引用没有及时释放,在使用池化 ByetBuffer 的情况下内存泄漏的问题就会产生。
28- 当采用了池化的 ByteBuffer 的时候,比如 PooledHeapByteBuf 和 PooledDirectByteBuf,其 deallocate()方法一共主要分为两个步骤。
27+ 当调用了 ByteBuf 的 ` release() ` 方法的时候,最后在上方的 ` release0() ` 方法中将会为 ByteBuf 的引用计数减一,当引用计数归于 0 的时候,将会调用 ` deallocate() ` 方法对其对应的底层存储数组进行释放(在池化的 ByteBuf 中,在 ` deallocate() ` 方法里会把该 ByteBuf 的 ` byte[] ` 回收到底层内存池中,以确保 ` byte[] ` 可以重复利用)。
28+
29+ 由于 Netty 中的 ByteBuf 并不是随着申请之后会马上使其引用计数归 0 而进行释放,往往在这两个操作之间还有许多操作,如果在这其中如果发生异常抛出导致引用没有及时释放,在使用池化 ByetBuffer 的情况下内存泄漏的问题就会产生。
30+
31+ 当采用了池化的 ByteBuffer 的时候,比如 PooledHeapByteBuf 和 PooledDirectByteBuf,其 ` deallocate() ` 方法一共主要分为两个步骤。
2932
3033``` java
3134@Override
@@ -40,10 +43,10 @@ protected final void deallocate() {
4043}
4144```
4245
43- - 将其底层的 byte[ ] 通过 free()方法回收到内存池中等待下一次使用。
44- - 通过 recycle()方法将其本身回收到对象池中等待下一次使用。
45- 关键在第一步的内存回收到池中,如果其引用计数未能在 ByteBuf 对象被回收之前归 0,将会导致其底层占用 byte[ ] 无法回收到内存池 PoolArena 中,导致该部分无法被重复利用,下一次将会申请新的内存进行操作,从而产生内存泄漏。
46- 而非池化的 ByteBuffer 即使引用计数没有在对象被回收的时候被归 0,因为其使用的是单独一块 byte[ ] 内存,因此也会随着 java 对象被回收使得底层 byte[ ] 被释放(由 JDK 的 Cleaner 来保证)。
46+ - 将其底层的 ` byte[] ` 通过 ` free() ` 方法回收到内存池中等待下一次使用。
47+ - 通过 ` recycle() ` 方法将其本身回收到对象池中等待下一次使用。
48+ 关键在第一步的内存回收到池中,如果其引用计数未能在 ByteBuf 对象被回收之前归 0,将会导致其底层占用 ` byte[] ` 无法回收到内存池 PoolArena 中,导致该部分无法被重复利用,下一次将会申请新的内存进行操作,从而产生内存泄漏。
49+ 而非池化的 ByteBuffer 即使引用计数没有在对象被回收的时候被归 0,因为其使用的是单独一块 ` byte[] ` 内存,因此也会随着 java 对象被回收使得底层 ` byte[] ` 被释放(由 JDK 的 Cleaner 来保证)。
4750
4851## Netty 进行内存泄漏检测的原理
4952
@@ -73,7 +76,7 @@ public boolean release(int decrement) {
7376}
7477```
7578
76- 在包装类中,如果该 ByteBuf 成功 deallocated 释放掉了其持有的 byte[ ] 数组将会调用 DefaultResourceLeak 的 close()方法来已通知当前 ByteBuf 已经释放了其持有的内存。
79+ 在包装类中,如果该 ByteBuf 成功 deallocated 释放掉了其持有的 byte[ ] 数组将会调用 DefaultResourceLeak 的 ` close() ` 方法来已通知当前 ByteBuf 已经释放了其持有的内存。
7780正是这个虚引用使得该 DefaultResourceLeak 对象被回收的时候将会被放入到与这个虚引用所对应的 ReferenceQueue 中。
7881
7982``` java
@@ -104,6 +107,6 @@ if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
104107}
105108```
106109
107- Netty 会在下一次 ByteBuf 的采样中通过 reportLeak()方法将 ReferenceQueue 中的 DefaultResourceLeak 取出并判断其对应的 ByteBuf 是否已经在其回收前调用过其 close()方法,如果没有,显然在池化 ByteBuf 的场景下内存泄漏已经产生,将会以 ERROR 日志的方式进行日志打印。
110+ Netty 会在下一次 ByteBuf 的采样中通过 reportLeak()方法将 ReferenceQueue 中的 DefaultResourceLeak 取出并判断其对应的 ByteBuf 是否已经在其回收前调用过其 ` close() ` 方法,如果没有,显然在池化 ByteBuf 的场景下内存泄漏已经产生,将会以 ERROR 日志的方式进行日志打印。
108111
109112以上内容可以结合 JVM 堆外内存的资料进行阅读。
0 commit comments