• DirectByterBuffer直接内存回收机制


        在处理文件复制,传输使用NIO可以申请Direct Memory,好处又很多,比如可以减少JVM的GC,提高文件传输的性能。坏处就是如果出现了直接内存OOM,比较难排查问题。理论上NIO可以申请的直接内存是除JVM内存空间之前的所有内存。我们也可以使用 -XX:MaxDirectMemorySize=512m 来指定能够占用的直接内存的大小。那么问题来了。既然直接内存不归JVM管理,那么就是JVM无法去回收直接内存。那直接内存是怎样被回收的呢?

        一、看了几篇比较优秀的博客,再结合源码。自己理解了一下直接内存的回收机制。下面先看看 DirectByteBuffer.java 这个类的构造函数

    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;
    
        }

        看到这个位置 代码点进去,很醒目的看到了有一个非常熟悉的方法: System.gc()。这个方法就是建议JVM进行一次Full GC。所以我们知道了。当我们每次申请一个DirectByteBuffer的时候,首先都是会执行这个System.gc()建议JVM进行一次Full GC 的。这是直接内存被回收的方式之一

        二、接下来我们再看一下这个Cleaner.clear()方法。在这里创建了一个 Deallocator 对象。

        Deallocator实现了Runnable,所以我们要看一下Deallocator这个类的run()方法,run方法中非常重要的一个native方法 unsafe.freeMemory() ,通过方法名字就知道这个方法是用来释放内存的

    public void run() {
        if (address == 0) {
            // Paranoia
            return;
        }
        unsafe.freeMemory(address);
        address = 0;
        Bits.unreserveMemory(size, capacity);
    }
    public static Cleaner create(Object var0, Runnable var1) {
            return var1 == null ? null : add(new Cleaner(var0, var1));
    }

    并把这个对象加到了Cleaner队列里面

    private static synchronized Cleaner add(Cleaner var0) {
            if (first != null) {
                var0.next = first;
                first.prev = var0;
            }
    
            first = var0;
            return var0;
    }

    Cleaner这个类继承了 PhantomReference

    public class Cleaner extends PhantomReference<Object>

    这是一个弱引用。在GC的时候就会触发。在Cleaner类中有一个clean()方法。触发了这个clean()方法,就会执行Deallocator的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;
                    }
                });
            }
    
        }
    }

         简单总结一下怎么NIO的直接内存是怎么内存回收的。在创建 DirectoryByteBuffer 类的时候就会触发一次 System.gc() 方法。对象创建的时候还会创建一个弱引用Cleaner对象,这个对象里面的clean()方法在GC的时候就会被触发,触发后就会执行Deallocator类的run方法。通过 unsafe.freeMemory() 方法,来释放内存。

    参考:

    【1】https://www.cnblogs.com/duanxz/p/6089485.html

    【2】https://zhuanlan.zhihu.com/p/161939673

  • 相关阅读:
    小知识!
    命令级的python静态资源服务。
    自定义滚动条样式-transition无效
    css:a:visited限制
    react16 渲染流程
    virtual-dom
    用video标签流式加载
    golang 代码笔记
    position:fixed not work?
    go/node/python 多进程与多核cpu
  • 原文地址:https://www.cnblogs.com/happyflyingpig/p/16208191.html
Copyright © 2020-2023  润新知