【问题标题】:Why do we need to manually handle reference counting for Netty ByteBuf if JVM GC is still in place?如果 JVM GC 仍然存在,为什么我们需要手动处理 Netty ByteBuf 的引用计数?
【发布时间】:2015-04-23 04:45:53
【问题描述】:

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

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

引用计数的权衡之一是用户必须 使用消息时要小心。虽然 JVM 仍然能够 GC 这样的消息(因为它不知道引用计数)这个 消息不会被放回可以从中获取消息的池中 前。因此,您很有可能会在 如果您不小心发布这些消息,则为一分。

还有一些相关的话题: 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 可以从两个角度进行分类:

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 对象被 GCed,它就完全消失了。所以下面是我认为 Netty 所做的:

  • 对于 (a),Netty 分配器必须诱使 JVM GC 相信对象永远不应该被 GC。然后使用引用计数将对象移出/移回池中。 这是另一种形式的生命周期

  • 对于 (b),不涉及 JVM GC,因为它不是基于 JVM 堆的。 Netty 分配器需要使用引用计数将对象移出/移回池中。

  • 对于(c),JVM GC 完全负责控制对象的生命周期。 Netty 分配器只是提供了分配对象的 API。

  • 对于 (d),不涉及 JVM GC。并且不需要池化。所以Netty分配器只需要提供分配/释放对象的API即可。

【问题讨论】:

标签: netty


【解决方案1】:

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

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

Java 中的池化对象是 polemic topic,但 Netty 这样做的选择似乎得到了回报,您引用的 Twitter article 显示了一些证据。对于分配缓冲区的特殊情况,当缓冲区的大小很大时,您可以看到它确实为直接缓冲区和堆缓冲区情况带来了好处。

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

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

【讨论】:

  • 现在正在为看起来像 Netty 缓冲区泄漏而苦苦挣扎,我想知道:为什么我们不能在抽象 ByteBuf 类中添加一个 finalize() 方法,然后释放该方法调用()?似乎可以解决问题,但我可能在这里忽略了一些东西?
  • @LeoGomes:“在这种情况下,缓冲区最终会被垃圾回收,但 Netty 的池不会知道这件事。”当池持有对缓冲区的引用时,如何对缓冲区进行垃圾回收?
【解决方案2】:

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

【讨论】:

  • 感谢您的回复,但我不这么认为。 ByteBuf 可以是基于堆的(我认为堆是指 JVM GC 堆),也可以是直接缓冲区(不是通过本机调用基于堆)或复合缓冲区。
  • 谢谢。该链接很有帮助。我需要一些时间来理解它。无论如何,我认为向 Netty 用户公开引用计数详细信息并不是一个好主意。
  • 你好 它可以使用本机库,而本机库又使用 malloc 等函数来直接访问内存。由于 jvm 无法控制,因此该内存变为堆外分配。最好的问候
  • 看来ByteBufAllocator 实现了自己的内存分配机制。所以它分配的内存应该都不受 JVM GC 的控制。但是我在netty的书《最常用的ByteBuf模式将数据存储在JVM的堆空间中》中读到了这个。这个怎么理解?这是否意味着ByteBufAllocator 也可以从 JVM 堆中分配内存?
  • 你好是的,它可以同时使用堆外和堆内存。但是在堆内存中不会引起任何问题,因为它由gc控制,而堆外内存没有任何控制,所以如果你忘记了要释放它,您会在一段时间内失去记忆。最好的问候
猜你喜欢
  • 2016-09-24
  • 2021-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-30
  • 2013-01-30
相关资源
最近更新 更多