堆内堆外
我们在Java中创建的对象都处于堆内内存(heap)中,
堆内内存是由JVM所管控的Java进程内存,并且它们遵循JVM的内存管理机制,
与之相对的是堆外内存,存在于JVM管控之外的内存区域,属于Java进程的内存
Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native方法
使用堆外内存的原因
- 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。
- 频繁的I/O.提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。
- 复制很大的文件
限制参数
直接内存的最大大小可以通过-XX:MaxDirectMemorySize来设置,默认是64M
分配
- Unsafe.allocateMemory() : 这个是native
- ByteBuffer.allocateDirect(): 这个调用上面的unsafe实现
native方法: 指的是自己写c/c++,然后供Java调用
DirectByteBuffer封装了Unsafe
DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现
Unsafe.setMemory进行内存初始化,
回收
构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起被释放
如何通过构建垃圾回收追踪对象Cleaner实现堆外内存释放呢?
Cleaner继承自Java四大引用类型之一的虚引用PhantomReference
无法通过虚引用获取与之关联的对象实例,且当对象仅被虚引用引用时,在任何发生GC的时候,其均可被回收
GC有一个高优先级的Reference-Handler守护进程,循环处理pending链表的对象引用,执行Cleaner的clean方法,通过Deallocator释放
Deallocator作为线程执行了unsafe的rreeMemory
Cleaner通过虚引用和持有的引用队列, 执行clean方法来调度Deallocator
直接内存和堆内存的比较
在比较两者的性能时,我们分两方面来说。
- 申请空间的耗时:堆内存比较快
- 读写的耗时:直接内存比较快,快是因为减少拷贝