- 直接内存申请空间其实是比较消耗性能,需要本地方法通过系统调用完成
- 直接内存在IO读写上的性能要优于堆内存,所以直接内存特别适合申请以后进行多次读写
- 堆外内存优势在 IO 操作上,对于网络 IO,使用 Socket 发送数据时,能够节省堆内存到堆外内存的数据拷贝
-
零拷贝
- 复制很大的文件
- 频繁的IO操作,例如网络并发场景
- 不受JVM堆的大小限制
- 会受到本机总内存(包括RAM及SWAP区或者分页文件)的大小及处理器寻址空间的限制,可能会抛出OutOfMemoryError异常
- 直接内存的最大大小可以通过-XX:MaxDirectMemorySize来设置,默认是64M
- ByteBuffer.allocateDirect()
- 由于申请内存前可能会调用 System.gc(),所以谨慎设置 -XX:+DisableExplicitGC 这个选项,这个参数作用是禁止代码中显示触发的 Full GC
-
回收
- 自动回收
- GC 时会扫描 DirectByteBuffer 对象是否有有效引用指向该对象,如没有,在回收 DirectByteBuffer 对象的同时且会回收其占用的堆外内存
-
虚引用(Phantom Reference)
- 也称为幽灵引用或者幻影引用
- 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
- 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
- GC过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,ReferenceHandler这个守护线程会处理pending队列里,执行一些后置处理,这里是调用Cleaner的clean方法
- 而DirectByteBuffer构造方法内创建了一个Cleaner对象, Cleaner继承了PhantomReference,其referent为DirectByteBuffer,也是通过Cleaner调用unsafe.freeMemory(address)来释放直接内存
-
手动回收
- DirectByteBuffer 实现了 DirectBuffer 接口,这个接口有 cleaner 方法可以获取 cleaner 对象。
public static void clean(final ByteBuffer byteBuffer) { if (byteBuffer.isDirect()) { ((DirectBuffer)byteBuffer).cleaner().clean(); } }
- Netty 中的堆外内存池就是使用反射来实现手动回收方式进行回收的。
- 当堆空间非常富余,直接内存占用虽然很高,但并不会自发引起 GC——哪怕直接内存已经用满。 如果不触发 GC,直接内存可能就会溢出。因此,只能人为去触发 GC,从而回收直接内存。
- DirectByteBuffer 这个类是 JDK 提供使用堆外内存的一种途径,当然常见的业务开发一般不会接触到,即使涉及到也可能是框架(如 Netty、RPC 等)使用的,对框架使用者来说也是透明的