一、堆外内存
Jdk8中原来的方法区变成了元空间,并且使用的是堆外内存,那么堆外内存是如何进行回收的
操作系统并不会自动释放Java程序使用的堆外内存。这是因为这些内存区域不是由Java虚拟机的垃圾回收器管理的,而是由Java程序直接通过本地方法或Unsafe等手段分配的。因此,一旦分配了堆外内存,就需要在不再使用它时显式释放。
堆内存由JVM自己管理,堆外内存必须要由我们自己释放
堆内存的消耗速度远远小于堆外内存的消耗,但要命的是必须先释放堆内存中的对象,才能释放堆外内存,但是我们又不能强制JVM释放堆内存。
Direct Memory的回收机制:
Direct Memory是受GC控制的,例如
ByteBuffer bb = ByteBuffer.allocateDirect(1024),
这段代码的执行会在堆外占用1k的内存,Java堆内只会占用一个对象的指针引用的大小,
堆外的这1k的空间只有当bb对象被回收时,才会被回收,这里会发现一个明显的不对称现象,
就是堆外可能占用了很多,而堆内没占用多少,导致还没触发GC,
那就很容易出现Direct Memory造成物理内存耗光。
Direct ByteBuffer回收机制
分配出去的内存其实也是由GC负责回收的,而不像Unsafe是完全自行管理的,
Hotspot在GC时会扫描Direct ByteBuffer对象是否有引用,如没有则同时也会回收其占用的堆外内存。
二、Direct Memory是什么?
直接内存(Direct Memory)是 Java 虚拟机(JVM)中的一种内存分配方式,主要用于提高 I/O 操作的性能。
与 Java 堆内存不同,直接内存的分配和释放不受Java堆大小的限制,而是通过本地方法接口(Native API)直接调用底层【操作系统】的【内存分配函数】来实现。
缺点:
1、分配和释放的开销较高: 与堆内存相比,直接内存的分配和释放通常更昂贵,因为涉及到与底层操作系统的交互。
2、容易导致内存泄漏: 直接内存的垃圾回收不受 JVM 的控制,因此需要开发者手动释放内存。如果不谨慎管理,容易导致内存泄漏。
3、不受Java堆限制: 直接内存的分配和释放不受 Java 堆大小的限制,因此需要特别注意防止过度使用直接内存导致系统资源耗尽。
小结:不受Java堆限制,并且需要手动释放
三、Direct ByteBuffer是什么?
四、Direct ByteBuffer和Direct Memory区别
Direct ByteBuffer 和 Direct Memory 都涉及到直接访问系统内存,但它们是不同的概念
Direct Memory是一个很大的概念,可以理解Direct ByteBuffer他的一种实现
五、申请堆外内存和释放堆外内存-案例
通过ByteBuffer.allocateDirect()方法分配堆外内存。看下allocateDirect方法的实现。
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
主要逻辑位于DirectByteBuffer类的构造方法中,在这个构造方法中设计到了内存的申请,释放。构造方法的主要执行流程和逻辑如下:
1、调用Unsafe.allocateMemory()方法正式地分配内存,查看HotSpot的native代码,
容易发现是调用了C语言的malloc()函数。
2、调用Unsafe.setMemory()方法将分配到的内存区域初始化为全0,自然是对应C语言的memset()函数。
3、调用Cleaner.create()方法创建一个sun.misc.Cleaner实例
(其中包含有DirectByteBuffer的内部类Deallocator)该实例具体负责后面的堆外内存回收
DirectByteBuffer是一个轻量级的对象,主要的信息都维护在静态内部类Deallocator中,包括内存基地址、大小等。
Cleaner是单独维护的。由以下代码可见,Deallocator还实现了Runnable接口,当它被执行时,会调用Unsafe.freeMemory()方法(对应C语言的free()函数)释放掉它持有的堆外内存。
涉及到内存的手动释放,就要用到C语言的函数来手动指定了。