ByteBuf是Netty网络通信框架中一个重要的组件。先进和友好的设计理念让开发者受益匪浅。
- 两个指针操作ByteBuf -> 读和写
- 对象池技术 -> 非垃圾回收机制
对象池技术1
3.对象池模式是一种软件创建设计模式,它使用一组可重用的对象 - “池” ,而不是按需分配和销毁它们。池的客户端从池中请求对象并对返回的对象执行操作。当客户端完成后,它将对象返回到池而不是销毁它,这可以手动或自动完成。
4.在某些情况下,对象池可显着提高性能。对象池使对象生存期复杂化,因为此时实际上并未创建或销毁从池中获取和返回到池的对象,因此需要小心实现。
5.常用在实例化开销大的对象。
Netty利用ByteBuf对象池2
BUFFER POOLING
6.Netty在对象池中利用引用计数器技术,为创建的ByteBuf对象设置引用计数器。
7.仅当引用计数器的值为0时,返回对象池回收。
8.新创建的引用计数对象的计数器的初始值为1:
ByteBuf buf = ctx.alloc().directBuffer(); assert buf.refCnt() == 1;
9.释放引用计数对象时,其引用计数器的值减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;
10.尝试访问引用计数器的值为0的引用计数对象将触发IllegalReferenceCountException异常:
assert buf.refCnt() == 0; try { buf.writeLong(0xdeadbeef); throw new Error("should not reach here"); } catch (IllegalReferenceCountExeception e) { // Expected }
11.谁来销毁引用计数对象
一般的经验法则是,最后访问引用计数对象的一方负责销毁引用计数对象。 进一步来说:
- 如果[发送]组件应该将引用计数对象传递给另一个[接收]组件,则发送组件通常不需要销毁它,而是将该决定推迟到接收组件。
- 如果组件使用引用计数对象并且知道其他任何内容都不再访问它(即,不传递给另一个组件引用),则组件应该销毁它。
为什么Netty会发生内存泄漏问题3
12.如果应用程序在使用ByteBuf后,没有调用方法release()(这个方法将其放回对象池中),又没有任何进一步的引用,则会发生泄漏。 在这种情况下,缓冲区最终将被GC(垃圾回收器)清除,但Netty的对象池不会知道这种情况。 然后,对象池将逐渐相信程序正在使用越来越多的永不返回池中的ByteBuf。这可能会产生内存泄漏。这可能会产生内存泄漏,因为ByteBuf被垃圾回收器回收,对象池回收不到它。导致对象池创建越来越多的新的引用计数对象。具体的Netty内部优化,请看Netty Internals - Optimizations everywhere
参考文献
https://en.wikipedia.org/wiki/Object_pool_pattern#Criticism ↩︎
https://netty.io/wiki/reference-counted-objects.html ↩︎
https://stackoverflow.com/questions/6697709/are-java-directbytebuffer-wrappers-garbage-collected ↩︎