Netty为什么要手动释放ByteBuf资源?

ByteBuf是Netty网络通信框架中一个重要的组件。先进和友好的设计理念让开发者受益匪浅。

  1. 两个指针操作ByteBuf -> 读和写
  2. 对象池技术 -> 非垃圾回收机制

对象池技术1

  1. 对象池模式是一种软件创建设计模式,它使用一组可重用的对象 - “池” ,而不是按需分配和销毁它们。池的客户端从池中请求对象并对返回的对象执行操作。当客户端完成后,它将对象返回到池而不是销毁它,这可以手动或自动完成。
  2. 在某些情况下,对象池可显着提高性能。对象池使对象生存期复杂化,因为此时实际上并未创建或销毁从池中获取和返回到池的对象,因此需要小心实现。
  3. 常用在实例化开销大的对象。

Netty利用ByteBuf对象池2
BUFFER POOLING

  1. Netty在对象池中利用引用计数器技术,为创建的ByteBuf对象设置引用计数器。
  2. 仅当引用计数器的值为0时,返回对象池回收。
  3. 新创建的引用计数对象的计数器的初始值为1:
ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;
  1. 释放引用计数对象时,其引用计数器的值减1。如果引用计数到达0,则引用计数对象将被释放或返回其对象池:
assert buf.refCnt() == 1;
// release() returns true only if the reference count becomes 0.
boolean destroyed = buf.release();
assert destroyed;
assert buf.refCnt() == 0;

  1. 尝试访问引用计数器的值为0的引用计数对象将触发IllegalReferenceCountException异常:
assert buf.refCnt() == 0;
try {
  buf.writeLong(0xdeadbeef);
  throw new Error("should not reach here");
} catch (IllegalReferenceCountExeception e) {
  // Expected
}

  1. 谁来销毁引用计数对象
    一般的经验法则是,最后访问引用计数对象的一方负责销毁引用计数对象。 进一步来说:
  • 如果[发送]组件应该将引用计数对象传递给另一个[接收]组件,则发送组件通常不需要销毁它,而是将该决定推迟到接收组件。
  • 如果组件使用引用计数对象并且知道其他任何内容都不再访问它(即,不传递给另一个组件引用),则组件应该销毁它。

为什么Netty会发生内存泄漏问题3

  1. 如果应用程序在使用ByteBuf后,没有调用方法release()(这个方法将其放回对象池中),又没有任何进一步的引用,则会发生泄漏。 在这种情况下,缓冲区最终将被GC(垃圾回收器)清除,但Netty的对象池不会知道这种情况。 然后,对象池将逐渐相信程序正在使用越来越多的永不返回池中的ByteBuf。这可能会产生内存泄漏。这可能会产生内存泄漏,因为ByteBuf被垃圾回收器回收,对象池回收不到它。导致对象池创建越来越多的新的引用计数对象。具体的Netty内部优化,请看Netty Internals - Optimizations everywhere

参考文献


  1. https://en.wikipedia.org/wiki/Object_pool_pattern#Criticism ↩︎

  2. https://netty.io/wiki/reference-counted-objects.html ↩︎

  3. https://stackoverflow.com/questions/6697709/are-java-directbytebuffer-wrappers-garbage-collected ↩︎

你可能感兴趣的:(Java学习,数据结构)