java堆外内存

堆外内存申请和释放:
堆外内存时不受jvm管理,gc算法不能被回收的。

如何申请和释放?

1. unsafe类

我们可以使用unsafe类来进行申请和释放:
申请:

// 分配 10M 堆外内存
long address = unsafe.allocateMemory(10 * 1024 * 1024);

释放:unsafe.freeMemory()

使用:

private static Unsafe unsafe = null;
static {
    try {
        Field getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        getUnsafe.setAccessible(true);
        unsafe = (Unsafe) getUnsafe.get(null);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

unsafe通过jni来进行调用,调用到c语言的unsafe_allocateMemory。
其实还是调用c的函数:malloc来申请内存
释放的c函数:free
java堆外内存_第1张图片

java堆外内存_第2张图片

2. NIO中也会有堆外内存的申请
// 分配 10M 堆外内存
ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024); 

跟一下源码: 底层调用的是DirectByteBuffer

 public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

调用了DirectByteBuffer

   DirectByteBuffer(int cap) {                   // package-private
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }

看到:

   base = unsafe.allocateMemory(size);

其实还是调用的unsafe类

那么NIO是如何释放的?主要在于cleaner类:

        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

DirectByteBuffer 在初始化时会创建一个 Cleaner 对象,并且 Deallocator 传入到了 cleaner,并且赋值到了thunk

sun.misc.Cleaner#create

 public static Cleaner create(Object paramObject, Runnable paramRunnable) {
        if (paramRunnable == null)
            return null; 
        return add(new Cleaner(paramObject, paramRunnable));
    }
private Cleaner(Object paramObject, Runnable paramRunnable) {
        super(paramObject, dummyQueue);
        this.thunk = paramRunnable;
    }

Cleaner是什么?

我们知道java中的四种引用方式:强引用 StrongReference、软引用 SoftReference、弱引用 WeakReference 和虚引用 PhantomReference

Cleaner 就属于 PhantomReference 的子类

sun.misc.Cleaner

public class Cleaner
    extends PhantomReference<Object>

那么 虚引用如何使用?
不能直接只用,而是需要使用ReferenceQueue (引用队列)

public class Cleaner extends java.lang.ref.PhantomReference<java.lang.Object> {

    private static final java.lang.ref.ReferenceQueue<java.lang.Object> dummyQueue;
    private static sun.misc.Cleaner first;
    private sun.misc.Cleaner next;
    private sun.misc.Cleaner prev;
    private final java.lang.Runnable thunk;
    public void clean() {}

}

Deallocator有什么用?
这个是用来回收堆外内存的
首先看一下cleaner的 clean()方法

 public void clean() {
        if (!remove(this))
            return; 
        try {
            this.thunk.run();
        } catch (Throwable throwable) {
          
        } 
    }

这个thunk就是 我们创建的时候 传入的 Deallocator 线程
再看一下这行代码 Deallocator:

        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

这个是DirectByteBuffer自己创建的线程,调用的是 unsafe.freeMemory(address);


    private static class Deallocator
        implements Runnable
    {

        private static Unsafe unsafe = Unsafe.getUnsafe();

        private long address;
        private long size;
        private int capacity;

        private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address;
            this.size = size;
            this.capacity = capacity;
        }

        public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            unsafe.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
        }

    }

当发生 GC 时,DirectByteBuffer 对象被回收
java堆外内存_第3张图片

Cleaner 对象不再有任何引用关系,在下一次 GC 时,该 Cleaner 对象将被添加到 ReferenceQueue 中,并执行 clean() 方法。clean() 方法主要做两件事情:

  1. 将 Cleaner 对象从 Cleaner 链表中移除;
  2. 调用 unsafe.freeMemory 方法清理堆外内存。

你可能感兴趣的:(java,java,开发语言,经验分享)