1. 程式人生 > 實用技巧 >記憶體結構篇:直接記憶體

記憶體結構篇:直接記憶體

一、定義

直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,也不是《Java虛擬機器規範》中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,而且也可能導致 OutOfMemoryError 異常出現。(即不屬於JVM虛擬機器記憶體區域,屬於作業系統的記憶體)

在 JDK1.4 中新引入了NIO類,它可以使用 Native 函式庫直接分配堆外記憶體,然後通過 Java 堆裡的 DirectByteBuffer 物件作為這塊記憶體的引用進行操作。這樣就能在一些場景中顯著提高效能,因為避免了在 Java 堆(堆記憶體)和 Native 堆(堆外記憶體)中來回複製資料。

  • 常見於 NIO 操作時,用於資料緩衝區
  • 分配回收成本較高,但讀寫效能高
  • 不受 JVM 記憶體回收管理
ByteBuffer.allocateDirect();	//分配直接記憶體空間
static final String FROM = "E:\\test.mp4";
static final String TO = "E:\\a.mp4";
static final int _1Mb = 1024 * 1024;

public static void main(String[] args) {
    io(); // io 用時:1535.586957 1766.963399 1359.240226
    directBuffer(); // directBuffer 用時:479.295165 702.291454 562.56592
}

private static void directBuffer() {
    long start = System.nanoTime();
    try (FileChannel from = new FileInputStream(FROM).getChannel();
         FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
        ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);	//分配一塊直接記憶體,系統和java程式都能訪問
        while (true) {
            int len = from.read(bb);
            if (len == -1) {
                break;
            }
            bb.flip();
            to.write(bb);
            bb.clear();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    long end = System.nanoTime();
    System.out.println("directBuffer 用時:" + (end - start) / 1000_000.0);
}

private static void io() {
    long start = System.nanoTime();
    try (FileInputStream from = new FileInputStream(FROM);
         FileOutputStream to = new FileOutputStream(TO);
        ) {
        byte[] buf = new byte[_1Mb];
        while (true) {
            int len = from.read(buf);
            if (len == -1) {
                break;
            }
            to.write(buf, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    long end = System.nanoTime();
    System.out.println("io 用時:" + (end - start) / 1000_000.0);
}

不使用直接記憶體進行讀寫:

使用直接記憶體進行讀寫:在作業系統劃出一個緩衝區作為直接記憶體,系統和java程式都能訪問,減少一個緩衝區的複製操作,加快檔案的讀寫

二、分配和回收原理

  • 使用了 Unsafe 物件完成直接記憶體的分配回收,並且回收需要主動呼叫 freeMemory 方法
/**
 * 直接記憶體分配的底層原理:Unsafe
 */
public class Demo {
    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        Unsafe unsafe = getUnsafe();
        // 分配記憶體
        long base = unsafe.allocateMemory(_1Gb);
        unsafe.setMemory(base, _1Gb, (byte) 0);
        System.in.read();

        // 釋放記憶體
        unsafe.freeMemory(base);
        System.in.read();
    }

    public static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
  • ByteBuffer 的實現類內部,使用了 Cleaner (虛引用)來監測 ByteBuffer 物件,一旦
    ByteBuffer 物件被垃圾回收,那麼就會由 ReferenceHandler 執行緒通過 Cleaner 的 clean 方法調
    用 freeMemory 來釋放直接記憶體
/**
 * 禁用顯式回收對直接記憶體的影響
 */
public class Demo1_26 {
    static int _1Gb = 1024 * 1024 * 1024;

    /*
     * -XX:+DisableExplicitGC 禁止顯式的垃圾回收
     */
    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);
        System.out.println("分配完畢...");
        System.in.read();
        System.out.println("開始釋放...");
        byteBuffer = null;
        System.gc(); // 顯式的垃圾回收,Full GC
        System.in.read();
    }
}