如果 JVM GC 仍在进行,为什么我们需要手动处理 Netty ByteBuf 的引用计数?

Why do we need to manually handle reference counting for Netty ByteBuf if JVM GC is still in place?

根据书本Netty in Action v10reference counting用于处理ByteBuf的池化。但是 JVM 并不知道 netty 引用计数,所以 JVM 仍然可以 GC ByteBuf。如果是这样,为什么我们还需要关心引用计数并手动调用release()方法?

我引用了《Netty in Action v10》一书中的一些内容来添加一些上下文。

One of the tradeoffs of reference-counting is that the user have to be carefully when consume messages. While the JVM will still be able to GC such a message (as it is not aware of the reference-counting) this message will not be put back in the pool from which it may be obtained before. Thus chances are good that you will run out of resources at one point if you do not carefully release these messages.

以及一些相关话题: Buffer ownership in Netty 4: How is buffer life-cycle managed?

https://blog.twitter.com/2013/netty-4-at-twitter-reduced-gc-overhead

加 1

(以下是我的一些理解。)

一个ByteBuf可以从2个角度来分类:

1. Pooled or Unpooled
2. Heap-based or Direct

所以可以有4种组合:

(a) Pooled Heap-based
(b) Pooled Direct
(c) Unpooled Heap-based
(d) Unpooled Direct

只有 (a) 和 (c) 受 JVM GC 机制影响,因为它们是基于堆的。

上面引用自,我认为message表示一个Java对象,属于(a)类

一个最终规则是,如果一个 Java 对象被 GC,它就完全消失了。下面是我认为 Netty 所做的:

ByteBuf 正在使用堆外内存,因此它对 GC 不可见。这就是为什么你需要更新引用计数(否则 netty 将不知道何时释放该项目)。 最好的问候

直接缓冲区由垃圾收集器间接释放。我会让您通读这个问题的答案以了解这是如何发生的:Are Java DirectByteBuffer wrappers garbage collected?

当您执行 I/O 操作时,堆缓冲区需要在被内核处理之前复制到直接内存。当您使用直接缓冲区时,您可以保存该复制操作,这是使用直接缓冲区的主要优势。一个缺点是直接内存分配比从 java 堆分配更昂贵,因此 Netty 引入了池化概念。

Java 中的池化对象是您引用的 polemic topic, but the Netty choice for doing so seems to have paid off and the Twitter article 显示的一些证据。对于分配缓冲区的特殊情况,当缓冲区的大小很大时,您可以看到它确实在直接缓冲区和堆缓冲区的情况下都带来了好处。

现在对于池化,当它们被池化时 GC 不会回收缓冲区,因为在您使用缓冲区时,您的应用程序有一个或多个对它的引用;或者 Netty 的池有一个对它的引用,当它刚刚被分配并且还没有给你的应用程序或者在你的应用程序使用它并将它还给池之后。

当您的应用程序在使用了一个缓冲区并且没有进一步引用它之后没有调用 release() 时,就会发生泄漏,这实际上意味着 将它放回池中,如果您没有任何进一步的参考。在这种情况下,缓冲区最终会被垃圾回收,但 Netty 的池不会知道。然后池会越来越相信您正在使用越来越多的缓冲区,这些缓冲区永远不会返回到池中。这可能会产生内存泄漏,因为即使缓冲区本身被垃圾收集,用于存储池的内部数据结构也不会。