文章目录
1. ByteBuffer 堆外内存介绍
在介绍OOM那篇文章中,对堆外内存进行了介绍,就直接把它复制过来;
ByteBuffer和DirectByteBuffer:
- ByteBuffer:字节缓冲区,它有两种实现:
- HeapByteBuffer:使用jvm堆内存的字节缓冲区;(对应 ByteBuffer源码中的 allocate()方法)
- DirectByteBuffer:使用堆外内存,不受jvm堆大小限制;(对应 ByteBuffer源码中的allocateDirect()方法)
- DirectByteBuffer:ByteBuffer对于使用堆外内存的实现,堆外内存直接使用unsafe方法请求堆外内存空间,读写数据;
- 源码:
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> { // ... 省略 public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); } // ... 省略 }
- 源码:
DirectByteBuffer与堆外内存的关系:
- 我们使用allocateDirect()方法生成的DirectByteBuffer对象,本身是存储在jvm堆中的,但是会在堆外内存中划分一块内存区域与这个对象关联起来;
- 在YGC或者FGC 回收 DirectByteBuffer对象的时候,会通过虚引用(对虚引用不了解的可以查看前面的文章)来释放它关联的堆外内存空间(由Cleaner类实现);
- Java NIO 在每次分配堆外内存会进行判断,如果堆外内存空间不足时,使用 System.gc() 尝试释放内存,再次进行判断;
堆外内存空间大小设置:
- jvm参数指定堆外内存大小:
-XX:MaxDirectMemorySize=512m
; - 但是如果没有手动指定时,用我们前面介绍的获取jvm参数的默认值命令:
java -XX:+PrintFlagsFinal -version | grep MaxDirectMemorySize
获取到的大小为0;
其实它这里是使用了VM类中代码来指定的大小:
也就是说堆外内存默认大小为64M;如果指定了jvm参数,就使用指定的大小值;public class VM { private static long directMemory = 64 * 1024 * 1024; public static long maxDirectMemory() { return directMemory; } }
2. ByteBuffer 堆外内存申请、释放(源码分析)
2.1 堆外内存申请
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 * 1024 * 1024);
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
在使用ByteBuffer的静态方法allocateDirect()申请内存时,会使用 DirectByteBuffer类的构造方法创建一个DirectByteBuffer对象:
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
// 判断是否有足够的空间可供申请
// size:根据是否按页对齐,得到的真实需要申请的内存大小
// cap:用户指定需要的内存大小(<=size)
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 调用 UNsafe方法申请内存
base = unsafe.