堆外内存

作者: 修行者12138 | 来源:发表于2024-04-02 14:38 被阅读0次

    什么是堆外内存

    堆外内存也叫直接内存(Direct Memory),并不是JVM内存区域的一部分,也不是《Java虚拟机规范》中定义的内存区域。
    JDK1.4引入了NIO包(new input/output),引入了一种基于通道(Channel)和缓冲区(Buffer)的IO方式,可以使用native函数直接分配堆外内存,然后通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作。在一些场景中可以显著提高性能,因为避免了在Java对和Native堆中来回复制数据。
    申请堆外内存时,如果空间不足,会抛出java.lang.OutOfMemoryError: Direct buffer memory

    堆外内存的优势

    1. 堆外内存受操作系统管理,不受JVM管理,可以减少JVM GC压力,减轻JVM垃圾回收对程序性能的影响。
    2. 提高IO效率
    3. 堆内内存属于用户态的缓冲区,堆外内存属于内核态的缓冲区
    4. 如果从堆向磁盘/网卡写数据,需要CPU把数据复制从用户态复制到内核态,再由操作系统DMA从内核态复制到磁盘/网卡。
    5. 如果直接从堆外内存向磁盘/网卡写数据,可以省略掉用户态到内核态的复制


      image.png

    堆外内存的缺点

    1. 需要自行分配和回收内存,增加了复杂度
    2. 如果回收内存处理不当,容易引发内存泄漏,并且难以排查
      堆外内存引发内存泄漏排查: https://mp.weixin.qq.com/s/55slokngVRgqEav6c3TxOA

    堆外内存的使用

    1. 调用java.nio.ByteBuffer的allocateDirect方法,最终会调用sun.misc.Unsafe#allocateMemory,相当于C++的malloc函数
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
    
    1. 可以通过设置-XX:MaxDirectMemorySize=10M控制堆外内存的使用上限

    使用场景

    1. 常驻在本地内存的本地缓存,比如国家、省份、城市信息等,长期驻扎在老年代。

    堆外内存的回收

    1. 等到full gc的时候垃圾回收
      堆外内存对应的Java对象引用在堆里,受JVM垃圾回收的管理,一旦该对象引用被回收,JVM会释放对应的堆外内存
    2. 程序手动回收
    private void clean (ByteBuffer byteBuffer) {
        if (byteBuffer.isDirect()) {
            ((DirectBuffer)byteBuffer).cleaner().clean();
        }
    }
    

    堆外内存缓存框架

    https://mp.weixin.qq.com/s/YYMnXRFyozHpsmRwN-vvGQ

    1. 引入依赖
    <dependency>
        <groupId>org.caffinitas.ohc</groupId>
        <artifactId>ohc-core</artifactId>
        <version>0.7.4</version>
    </dependency>
    
    1. 自定义序列化、反序列器,调用put、get方法进行写入和读取,操作类似HashMap
    import org.apache.commons.io.Charsets;
    import org.caffinitas.ohc.CacheSerializer;
    import org.caffinitas.ohc.OHCache;
    import org.caffinitas.ohc.OHCacheBuilder;
    
    import java.nio.ByteBuffer;
    
    public class OhcDemo {
        public static void main(String[] args) {
            OHCache ohCache = OHCacheBuilder.<String, String>newBuilder()
                    .keySerializer(OhcDemo.stringSerializer)
                    .valueSerializer(OhcDemo.stringSerializer)
                    .build();
            ohCache.put("hello","why");
            System.out.println("ohCache.get(hello) = " + ohCache.get("hello"));
        }
    
        public static final CacheSerializer<String> stringSerializer = new CacheSerializer<String>() {
            public void serialize(String s, ByteBuffer buf) {
                // 得到字符串对象UTF-8编码的字节数组
                byte[] bytes = s.getBytes(Charsets.UTF_8);
                // 用前16位记录数组长度
                buf.put((byte) ((bytes.length >>> 8) & 0xFF));
                buf.put((byte) ((bytes.length) & 0xFF));
                buf.put(bytes);
            }
    
            public String deserialize(ByteBuffer buf) {
                // 获取字节数组的长度
                int length = (((buf.get() & 0xff) << 8) + ((buf.get() & 0xff)));
                byte[] bytes = new byte[length];
                // 读取字节数组
                buf.get(bytes);
                // 返回字符串对象
                return new String(bytes, Charsets.UTF_8);
            }
    
            public int serializedSize(String s) {
                byte[] bytes = s.getBytes(Charsets.UTF_8);
                // 设置字符串长度限制,2^16 = 65536
                if (bytes.length > 65535)
                    throw new RuntimeException("encoded string too long: " + bytes.length + " bytes");
    
                return bytes.length + 2;
            }
        };
    }
    

    相关文章

      网友评论

        本文标题:堆外内存

        本文链接:https://www.haomeiwen.com/subject/xeywtjtx.html