直接内存(DirectByteBuffer)
1.为什么使用直接内存:
当进行网络 I/O 操作、文件读写时,堆内内存都需要转换为堆外内存,然后再与底层设备进行交互,所以直接使用堆外内存可以减少一次内存拷贝。
2.如何使用直接内存:
在nio中,通过创建DirectByteBuffer对象(ByteBuffer.allocateDirect())来完成,在创建DirectByteBuffer时,内部
使用unsafe.allocateDirect来分配直接内存.DirectByteBuffer中的memory指向这块内存。
3.直接内存如何回收:(依赖GC)
在gc时,DirectByteBuffer如果被回收,那么与之关联的直接内存也会被回收。原理:在构建DirectByteBuffer时,
会构建一个Cleaner,这个Cleaner是一个虚引用; 当DirectByteBuffer被gc之后,这个对应的虚应用会被加入到pending列表(具体代码看jdk的Reference类),然后判断当前应用是否是Cleaner,是的话执行 clean() 方法。在clean() 方法中,会调用 unsafe.freeMemory 方法清理堆外内存。
4.直接内存默认大小和-Xmx指定的最大堆大小一样大 -XX:+MaxDirectMemorySize。
注意:DirectByteBuffer对象可能长时间得不到回收,那么分配的直接内存即使不用了,也无法回收掉。为了防止物理内存耗尽,
需要设置-XX:+MaxDirectMemorySize
Netty中的ByteBuf
Netty中的ByteBuf相较于jdk中的ByteBuffer具有如下优势
1.ByteBuf提供读写指针。在操作数据时不需要进行flip操作
2.支持基于引用计数器的回收策略。不像ByteBuffer那么死板,需要gc才能回收。
3.提供池化操作,不用频繁分配内存。
4.容量可以按需进行扩容
5.Netty提供了池化/非池化,直接/堆,unsafe/非unsafe等组合类型的ByteBuf
引用计数
通过引用计数,可以快速释放内存。ByteBuf都实现了AbstractReferenceCountedByteBuf,当使用完之后,手动调用release方法。
@Override
public boolean release(int decrement) {
return handleRelease(updater.release(this, decrement));
}
private boolean handleRelease(boolean result) {
if (result) {
deallocate();
}
return result;
}
/**
* Called once {@link #refCnt()} is equals 0.
*/
protected abstract void deallocate();
1.用非池化的直接内存:UnpooledDirectByteBuf举例。通过调用release方法,就可以将直接内存释放掉。相较于DirectByteBuffer来说
是一个大的优势。
@Override
protected void deallocate() {
ByteBuffer buffer = this.buffer;
if (buffer == null) {
return;
}
this.buffer = null;
//释放直接内存。
if (!doNotFree) {
freeDirect(buffer);
}
}
2.对于池化的B引用计数对于 Netty 设计缓存池化有非常大的帮助,当引用计数为 0,该 ByteBuf 可以被放入到对象池中,避免每次使用 ByteBuf 都重复创建,对于实现高性能的内存管理有着很大的意义。
此外 Netty 可以利用引用计数的特点实现内存泄漏检测工具。JVM 并不知道 Netty 的引用计数是如何实现的,当 ByteBuf 对象不可达时,一样会被 GC 回收掉,但是如果此时 ByteBuf 的引用计数不为 0,那么该对象就不会释放或者被放入对象池,从而发生了内存泄漏
浙公网安备 33010602011771号