JAVA基础:DirectByteBuffer

生活

这是我们这个行业的一件咄咄怪事:我们不仅不从错误中学习,我们也不从成功中学习。

今天来研究下DirectByteBuffer,本来要在IO/NIO的部分才会研究到,不过昨天讲到了虚引用,所以觉得还是有必要先行了解一下,加深印象。

虚引用

先来复习下昨天的虚引用:
被虚引用引用到的对象对其生命周期没有任何作用,与没有虚引用的对象是一样的,随时可能被JVM回收掉。
只是虚引用必须与一个引用队列相关联,当这个虚引用要被JVM回收时,就会进入引用队列,当在引用队列发现这个虚引用,也就知道这个引用相关联的对象要被回收了,在此之前会做一些操作。

堆外内存

堆外内存:
与堆外内存相反的就是堆内存中,我们创建的对象都是在堆内内存中的,也就是受JVM的管控的,在GC的过程中可能会被JVM回收。But堆外内存,指的时JVM堆以外的内存,这部分内存并不受JVM管控,也就不能被JVM直接回收了。

什么是DirectByteBuffer?

DirectByteBuffer顾名思义直接缓冲,我们可以使用它进行堆外内存的分配/使用/回收。

DirectByteBuffer 申请

//使用如下代码申请
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

//调用到 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 {
        //调用native方法分配堆外内存,并返回基地址
            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;



    }

DirectByteBuffer 回收

当直接内存使用完毕,DirectByteBuffer是可以被JVM回收的,但是通过它分配到的堆外内存却不能被JVM回收。那怎么办呢,如果有大量的堆外内存回收不了,就会造成内存泄漏。
那么这块直接内存是如何被回收的呢?
从清理器Cleaner入手:
1、从Cleaner的实现得知它是一个虚引用,在从虚引用的定义得知,虚引用必须与一个引用队列相关联,在jvm回收一个对象,发现它有虚引用时,就会把这个对象放入到引用队列中,做一系列操作。

private Cleaner(Object var1, Runnable var2) {
        super(var1, dummyQueue);
        this.thunk = var2;
    }

    public static Cleaner create(Object var0, Runnable var1) {
        return var1 == null ? null : add(new Cleaner(var0, var1));
    }

2、虚引用的基类启动了一个守护线程,当发现引用队列里有Cleaner对象时,执行clean方法

//清理时 执行trunk的run方法
public void clean() {
        if (remove(this)) {
            try {
                this.thunk.run();
            } catch (final Throwable var2) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null) {
                            (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                        }

                        System.exit(1);
                        return null;
                    }
                });
            }

        }
    }
//执行到 Deallocator run方法来释放堆外内存
public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            unsafe.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
        }

什么时候用到它?

进行文件复制时

使用它的优点?

1、减少了GC [GC会暂停其他操作]
2、加快了复制的速度

DirectByteBuffer 可能会出现什么问题?

1、在6u32前的版本里,CMS GC有bug会导致可能回收不掉
2、DirectByteBuffer晋升到年老代,必须要等到full gc 才有可能被回收。当DirectByteBuffer使用较多且存活时间过长的情况时,很有可能造成堆外内存OOM

如何判断是 DirectByteBuffer ?

当堆外内存占用较多时,可以尝试强制执行Full Gc看堆外内存是否有下降,如果有下降就很有可能时DirectByteBuffer的原因了。

最好是在启动参数上增加-XX:MaxDirectMemorySize=x[m|g],例如-XX:MaxDirectMemorySize=500m
最大分配的堆外内存500M,超过这个范围就是OOM

猜你喜欢

转载自blog.csdn.net/qq_28605513/article/details/84873264