java

堆外内存

2024-04-02  本文已影响0人  修行者12138

什么是堆外内存

堆外内存也叫直接内存(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;
        }
    };
}
上一篇下一篇

猜你喜欢

热点阅读