• jvm学习(一)DirectByteBuffer堆外内存浅析


    问题

    • 堆内外内存的区别是什么

    堆内外内存

    java 进程的内存占用到底是怎么样的呢?

    1297993-20200707181329640-409268722.png

    我们都知道 jvm 有垃圾回收机制,并且回收的重点区域就是堆,假如我们以堆内堆外来区分内存区域,上图所示

    • 堆内 A1
    • 堆外 B1 + B2 B1 有可能是 DirectByteBuffer 分配的堆外内存,而 B2 是 Native Code 分配的内存。

    DirectByteBuffer 类

    以下描述代码图片来自 : https://blog.csdn.net/mycs2012/java/article/details/93513057 , 非原创 
    

    DirectByteBuffer 使用

    public static void main(String[] args) throws Exception {
    
    	// 分配
    	ByteBuffer buffer = ByteBuffer.allocateDirect(128);
    
    	// 写入
    	buffer.put("写入到直接内存".getBytes(Charset.forName("utf-8")));
    
    	// 读取
    	buffer.flip();
    	byte[] bytes = new byte[buffer.remaining()];
    	buffer.get(bytes);
    	System.out.println(new String(bytes, Charset.forName("utf-8")));
    
    	System.gc();	// 不是必须
    }
    
    

    内部原理

    下面是 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,后面按size进行实际内存占用
        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;
    }
    
    

    主要的操作有 : 1、通过unsafe.allocateMemory(size)分配一段大小为size的内存,这是个native方法,表明会通过JNI调用操作系统本地的系统调用接口。该方法最终会调用操作系统的malloc方法,进行内存的分配,分配成功后返回一个基地址,这个基地址最后转换为address,DirectByteBuffer对象就是通过address和size引用这段内存。

    2、创建Cleaner对象,后续用于清理直接内存。

    而这个 Cleaner 类是如何达到回收内存的效果的呢 , Cleaner 对象会持有Deallocator,在执行收集的时候调用其 run 方法

    public class Cleaner extends PhantomReference<Object> { 
        ....
    }
    

    Cleaner 是虚引用的之类,虚引用的容易被回收,当被回收就回调用 Cleaner 的 clean 方法

    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方法释放内存
                unsafe.freeMemory(address);
                address = 0;
                // 更新统计变量
                Bits.unreserveMemory(size, capacity);
            }
     
        }
    
    

    参考

    • https://mp.weixin.qq.com/s/K-6CPo1haIe65KZPdTHSrA(堆外内存泄漏分析)
    • https://blog.csdn.net/mycs2012/article/details/93513057 (DirectByteBuffer 原理文章)
  • 相关阅读:
    golang strings.NewReader
    Nginx REWRITE阶段
    golang 条件变量详解
    redis的使用
    Python 高级(二)
    Python 高级
    面向对象(二)
    面向对象(一)
    Web服务器
    网络编程
  • 原文地址:https://www.cnblogs.com/Benjious/p/13274438.html
Copyright © 2020-2023  润新知