jvm直接內存(分配與回收)


1、定義

(1)常見於NIO操作時,用於數據緩沖區

(2)分配回收成本較高(屬於操作系統內存),但讀寫性能高

(3)不受JVM內存回收管理(依舊存在內存溢出的問題)

 

2、直接內存基本使用(IO操作舉例)

(1)分為兩步操作:

 (2)使用直接內存后,可以減少步驟:

 

3、直接內存導致的內存溢出問題

書寫程序:每次都分配直接內存,直到內存溢出

public class Test1 {
    static int _100Mb=1024*1024*100;

    public static void main(String[] args) {
        List<ByteBuffer> list=new ArrayList<>();
        int i=0;
        try {
           while (true){
               ByteBuffer byteBuffer=ByteBuffer.allocateDirect(_100Mb);
               list.add(byteBuffer);
               i++;
           }
        }finally {
            System.out.println(i);
        }
    }
}

測試結果:

17
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
    at java.nio.Bits.reserveMemory(Bits.java:694)
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
    at pers.zhb.test.Test1.main(Test1.java:15)

 

4、直接內存的分配與回收(底層通過Unsafe對象管理)

(1)直接內存的分配與回收

運行程序前:

 直接內存的分配與釋放程序:

public class Test1 {
    static int _1Gb=1024*1024*1024;
    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();
    }
}

分配直接內存后:

 在IDEA的控制台點擊回車對內存進行釋放:

 控制台打印出分配與回收的提示:

分配完畢

開始釋放

Process finished with exit code 0

其中System.gc() 回收掉byteBuffer對象

(2)Unsafe實現對直接內存的分配與回收:

public class Test1 {
    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(){
        Field field= null;
        try {
            field = Unsafe.class.getDeclaredField("theUnsafe");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        field.setAccessible(true);
        Unsafe unsafe= null;
        try {
            unsafe = (Unsafe)field.get(null);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return unsafe;
    }
}

jvm的內存分配與回收是自動的,不需要手動調用任何的方法,但是直接內存需要我們手動調用方法

 

5、ByteBuffer源碼

(1)ByteBuffer :

ByteBuffer byteBuffer= ByteBuffer.allocateDirect(_1Gb);

(2)allocateDirect:

 public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

(3)DirectByteBuffer

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.create(this, new Deallocator(base, size, cap));
        att = null;



    }

底層用到的依舊是unsafe對象

 

6、優缺點

(1)優點

  • 減少了垃圾回收

堆外內存是直接受操作系統管理(不是JVM)。這樣做能保持一個較小的堆內內存,以減少垃圾收集對應用的影響。

  • 提升IO速度

堆內內存由JVM管理,屬於“用戶態”;而堆外內存由OS管理,屬於“內核態”。如果從堆內向磁盤寫數據時,數據會被先復制到堆外內存,即內核緩沖區,然后再由OS寫入磁盤,使用堆外內存避免了這個操作。

(2)缺點

  缺點就是沒有JVM協助管理內存,需要我們自己來管理堆外內存,防止內存溢出。為了避免一直沒有FULL GC,最終導致物理內存被耗完。我們會指定直接內存的最大值,通過-XX:MaxDirectMemerySize來指定,當達到閾值的時候,調用system.gc來進行一次full gc,把那些沒有被使用的直接內存回收掉。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM