JVM中的直接内存

7 篇文章 0 订阅

1 定义

JVM内存结构图

直接内存并不属于JVM的内存结构,它是物理机的内存,但是JVM虚拟机可以调用该部分内存。

直接内存的使用:

  • 常见于NIO,用于数据缓冲区
  • 分配回收的代价较高,但是速度很快
  • 不收JVM内存回收管理

2 正常IO读取

正常IO读取结构图

从上图的结构图中,我们可以看到,当java程序需要读取文件时,首先会在java堆内存中new一个缓冲区,然后系统内存从磁盘中读取文件,再然后在将系统缓冲区中的字节流复制到java堆内存的缓冲区中,然后在由java程序调用。

这样做有一个缺点,就是需要开启两块内存,效率会很低。

3 直接内存读取

直接内存结构图

从图中可以看到,当java程序使用直接内存时,首先java程序在系统内存中分配一块直接内存块,这一内存块是系统内存和java堆内存可以共享的,那么系统内存读取到的磁盘文件就可以直接由java堆内存使用,这样就省去了复制的操作,大大节约了时间开销。

4 直接内存分配内存

通过java中的unsafe对象分配一块直接内存,直接内存大小在分配时指定。直接内存由于不受JVM的管理,所以直接内存的释放,必须主动调用unsafe对象进行释放,才能将直接内存释放。

这是ByteBuffer的分配直接内存的源码:

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对象关联了当前的ByteBuffer对象,因为ByteBuffer对象是受java虚拟机管理的,直接内存不受java虚拟机管理,所以这里的关联,就是为了在当ByteBuffer被释放的时候,直接内存也被释放,只不过是被unsafe对象释放的,并不是Java虚拟机释放的。
    	*/
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//释放直接内存
        att = null;
    }

cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//释放直接内存
解释:这里使用cleaner对象用来释放直接内存,cleaner对象关联了当前的ByteBuffer对象,因为ByteBuffer对象是受java虚拟机管理的,直接内存不受java虚拟机管理,所以这里的关联,就是为了在当ByteBuffer被释放的时候,直接内存也被释放,只不过是被unsafe对象释放的,并不是Java虚拟机释放的。

cleaner对象是一个虚引用对象。

Deallocator类源码:

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

    }

5 直接内存回收

从第4节我们可以看到,直接内存的会随着ByteBuffer对象的被回收,然后触发cleaner对象,调用Unsafe对象将直接内存回收,看起来也像是一种自动回收的方法。

但是,由于ByteBuffer对象的回收,是遵循JVM回收机制的,也就是说,得达到一定的回收条件才会回收ByteBuffer对象。那么直接内存也不会被回收,这样就会导致内存不足。所以建议使用手动调用Unsafe的方法释放直接内存。

写在最后

欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。
微信公众号

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值