中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

JDK 源碼閱讀 : DirectByteBuffer

2018-09-12    來源:importnew

容器云強(qiáng)勢上線!快速搭建集群,上萬Linux鏡像隨意使用

在文章JDK源碼閱讀-ByteBuffer中,我們學(xué)習(xí)了ByteBuffer的設(shè)計。但是他是一個抽象類,真正的實現(xiàn)分為兩類:HeapByteBufferDirectByteBuffer。HeapByteBuffer是堆內(nèi)ByteBuffer,使用byte[]存儲數(shù)據(jù),是對數(shù)組的封裝,比較簡單。DirectByteBuffer是堆外ByteBuffer,直接使用堆外內(nèi)存空間存儲數(shù)據(jù),是NIO高性能的核心設(shè)計之一。本文來分析一下DirectByteBuffer的實現(xiàn)。

如何使用DirectByteBuffer

如果需要實例化一個DirectByteBuffer,可以使用java.nio.ByteBuffer#allocateDirect這個方法:

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

DirectByteBuffer實例化流程

我們來看一下DirectByteBuffer是如何構(gòu)造,如何申請與釋放內(nèi)存的。先看看DirectByteBuffer的構(gòu)造函數(shù):

DirectByteBuffer(int cap) {                   // package-private
	// 初始化Buffer的四個核心屬性
    super(-1, 0, cap, cap);
    // 判斷是否需要頁面對齊,通過參數(shù)-XX:+PageAlignDirectMemory控制,默認(rèn)為false
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    // 確保有足夠內(nèi)存
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        // 調(diào)用unsafe方法分配內(nèi)存
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        // 分配失敗,釋放內(nèi)存
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    // 初始化內(nèi)存空間為0
    unsafe.setMemory(base, size, (byte) 0);
    // 設(shè)置內(nèi)存起始地址
    if (pa && (base % ps != 0)) {
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    // 使用Cleaner機(jī)制注冊內(nèi)存回收處理函數(shù)
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

申請內(nèi)存前會調(diào)用java.nio.Bits#reserveMemory判斷是否有足夠的空間可供申請:

// 該方法主要用于判斷申請的堆外內(nèi)存是否超過了用例指定的最大值
// 如果還有足夠空間可以申請,則更新對應(yīng)的變量
// 如果已經(jīng)沒有空間可以申請,則拋出OOME
// 參數(shù)解釋:
//     size:根據(jù)是否按頁對齊,得到的真實需要申請的內(nèi)存大小
//     cap:用戶指定需要的內(nèi)存大小(<=size)
static void reserveMemory(long size, int cap) {
    // 因為涉及到更新多個靜態(tài)統(tǒng)計變量,這里需要Bits類鎖
    synchronized (Bits.class) {
        // 獲取最大可以申請的對外內(nèi)存大小,默認(rèn)值是64MB
        // 可以通過參數(shù)-XX:MaxDirectMemorySize=<size>設(shè)置這個大小
        if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
        }
        // -XX:MaxDirectMemorySize限制的是用戶申請的大小,而不考慮對齊情況
        // 所以使用兩個變量來統(tǒng)計:
        //     reservedMemory:真實的目前保留的空間
        //     totalCapacity:目前用戶申請的空間
        if (cap <= maxMemory - totalCapacity) {
            reservedMemory += size;
            totalCapacity += cap;
            count++;
            return; // 如果空間足夠,更新統(tǒng)計變量后直接返回
        }
    }

    // 如果已經(jīng)沒有足夠空間,則嘗試GC
    System.gc();
    try {
        Thread.sleep(100);
    } catch (InterruptedException x) {
        // Restore interrupt status
        Thread.currentThread().interrupt();
    }
    synchronized (Bits.class) {
        // GC后再次判斷,如果還是沒有足夠空間,則拋出OOME
        if (totalCapacity + cap > maxMemory)
            throw new OutOfMemoryError("Direct buffer memory");
        reservedMemory += size;
        totalCapacity += cap;
        count++;
    }
}

java.nio.Bits#reserveMemory方法中,如果空間不足,會調(diào)用System.gc()嘗試釋放內(nèi)存,然后再進(jìn)行判斷,如果還是沒有足夠的空間,拋出OOME。

如果分配失敗,則需要把預(yù)留的統(tǒng)計變量更新回去:

static synchronized void unreserveMemory(long size, int cap) {
    if (reservedMemory > 0) {
        reservedMemory -= size;
        totalCapacity -= cap;
        count--;
        assert (reservedMemory > -1);
    }
}

從上面幾個函數(shù)中我們可以得到信息:

  1. 可以通過-XX:+PageAlignDirectMemor參數(shù)控制堆外內(nèi)存分配是否需要按頁對齊,默認(rèn)不對齊。
  2. 每次申請和釋放需要調(diào)用調(diào)用Bits的reserveMemoryunreserveMemory方法,這兩個方法根據(jù)內(nèi)部維護(hù)的統(tǒng)計變量判斷當(dāng)前是否還有足夠的空間可供申請,如果有足夠的空間,更新統(tǒng)計變量,如果沒有足夠的空間,調(diào)用System.gc()嘗試進(jìn)行垃圾回收,回收后再次進(jìn)行判斷,如果還是沒有足夠的空間,拋出OOME。
  3. Bits的reserveMemory方法判斷是否有足夠內(nèi)存不是判斷物理機(jī)是否有足夠內(nèi)存,而是判斷JVM啟動時,指定的堆外內(nèi)存空間大小是否有剩余的空間。這個大小由參數(shù)-XX:MaxDirectMemorySize=<size>設(shè)置。
  4. 確定有足夠的空間后,使用sun.misc.Unsafe#allocateMemory申請內(nèi)存
  5. 申請后的內(nèi)存空間會被清零
  6. DirectByteBuffer使用Cleaner機(jī)制進(jìn)行空間回收

可以看出除了判斷是否有足夠的空間的邏輯外,核心的邏輯是調(diào)用sun.misc.Unsafe#allocateMemory申請內(nèi)存,我們看一下這個函數(shù)是如何申請對外內(nèi)存的:

// 申請一塊本地內(nèi)存。內(nèi)存空間是未初始化的,其內(nèi)容是無法預(yù)期的。
// 使用freeMemory釋放內(nèi)存,使用reallocateMemory修改內(nèi)存大小
public native long allocateMemory(long bytes);
// openjdk8/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
  UnsafeWrapper("Unsafe_AllocateMemory");
  size_t sz = (size_t)size;
  if (sz != (julong)size || size < 0) {
    THROW_0(vmSymbols::java_lang_IllegalArgumentException());
  }
  if (sz == 0) {
    return 0;
  }
  sz = round_to(sz, HeapWordSize);
  // 調(diào)用os::malloc申請內(nèi)存,內(nèi)部使用malloc函數(shù)申請內(nèi)存
  void* x = os::malloc(sz, mtInternal);
  if (x == NULL) {
    THROW_0(vmSymbols::java_lang_OutOfMemoryError());
  }
  //Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
  return addr_to_java(x);
UNSAFE_END

可以看出sun.misc.Unsafe#allocateMemory使用malloc這個C標(biāo)準(zhǔn)庫的函數(shù)來申請內(nèi)存。

DirectByteBuffer回收流程

在DirectByteBuffer的構(gòu)造函數(shù)的最后,我們看到了這樣的語句:

// 使用Cleaner機(jī)制注冊內(nèi)存回收處理函數(shù)
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

這是使用Cleaner機(jī)制進(jìn)行內(nèi)存回收。因為DirectByteBuffer申請的內(nèi)存是在堆外,DirectByteBuffer本身支持保存了內(nèi)存的起始地址而已,所以DirectByteBuffer的內(nèi)存占用是由堆內(nèi)的DirectByteBuffer對象與堆外的對應(yīng)內(nèi)存空間共同構(gòu)成。堆內(nèi)的占用只是很小的一部分,這種對象被稱為冰山對象。

堆內(nèi)的DirectByteBuffer對象本身會被垃圾回收正常的處理,但是對外的內(nèi)存就不會被GC回收了,所以需要一個機(jī)制,在DirectByteBuffer回收時,同時回收其堆外申請的內(nèi)存。

Java中可選的特性有finalize函數(shù),但是finalize機(jī)制是Java官方不推薦的,官方推薦的做法是使用虛引用來處理對象被回收時的后續(xù)處理工作,可以參考JDK源碼閱讀-Reference。同時Java提供了Cleaner類來簡化這個實現(xiàn),Cleaner是PhantomReference的子類,可以在PhantomReference被加入ReferenceQueue時觸發(fā)對應(yīng)的Runnable回調(diào)。

DirectByteBuffer就是使用Cleaner機(jī)制來實現(xiàn)本身被GC時,回收堆外內(nèi)存的能力。我們來看一下其回收處理函數(shù)是如何實現(xiàn)的:

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方法釋放內(nèi)存
            unsafe.freeMemory(address);
            address = 0;
            // 更新統(tǒng)計變量
            Bits.unreserveMemory(size, capacity);
        }

    }

sun.misc.Unsafe#freeMemory方法使用C標(biāo)準(zhǔn)庫的free函數(shù)釋放內(nèi)存空間。同時更新Bits類中的統(tǒng)計變量。

DirectByteBuffer讀寫邏輯

public ByteBuffer put(int i, byte x) {
    unsafe.putByte(ix(checkIndex(i)), ((x)));
    return this;
}

public byte get(int i) {
    return ((unsafe.getByte(ix(checkIndex(i)))));
}

private long ix(int i) {
    return address + (i << 0);
}

DirectByteBuffer使用sun.misc.Unsafe#getByte(long)sun.misc.Unsafe#putByte(long, byte)這兩個方法來讀寫堆外內(nèi)存空間的指定位置的字節(jié)數(shù)據(jù)。不過這兩個方法本地實現(xiàn)比較復(fù)雜,這里就不分析了。

默認(rèn)可以申請的堆外內(nèi)存大小

上文提到了DirectByteBuffer申請內(nèi)存前會判斷是否有足夠的空間可供申請,這個是在一個指定的堆外大小限制的前提下。用戶可以通過-XX:MaxDirectMemorySize=<size>這個參數(shù)來控制可以申請多大的DirectByteBuffer內(nèi)存。但是默認(rèn)情況下這個大小是多少呢?

DirectByteBuffer通過sun.misc.VM#maxDirectMemory來獲取這個值,可以看一下對應(yīng)的代碼:

// A user-settable upper limit on the maximum amount of allocatable direct
// buffer memory.  This value may be changed during VM initialization if
// "java" is launched with "-XX:MaxDirectMemorySize=<size>".
//
// The initial value of this field is arbitrary; during JRE initialization
// it will be reset to the value specified on the command line, if any,
// otherwise to Runtime.getRuntime().maxMemory().
//
private static long directMemory = 64 * 1024 * 1024;

// Returns the maximum amount of allocatable direct buffer memory.
// The directMemory variable is initialized during system initialization
// in the saveAndRemoveProperties method.
//
public static long maxDirectMemory() {
    return directMemory;
}

這里directMemory默認(rèn)賦值為64MB,那對外內(nèi)存的默認(rèn)大小是64MB嗎?不是,仔細(xì)看注釋,注釋中說,這個值會在JRE啟動過程中被重新設(shè)置為用戶指定的值,如果用戶沒有指定,則會設(shè)置為Runtime.getRuntime().maxMemory()。

這個過程發(fā)生在sun.misc.VM#saveAndRemoveProperties函數(shù)中,這個函數(shù)會被java.lang.System#initializeSystemClass調(diào)用:

public static void saveAndRemoveProperties(Properties props) {
    if (booted)
        throw new IllegalStateException("System initialization has completed");

    savedProps.putAll(props);

    // Set the maximum amount of direct memory.  This value is controlled
    // by the vm option -XX:MaxDirectMemorySize=<size>.
    // The maximum amount of allocatable direct buffer memory (in bytes)
    // from the system property sun.nio.MaxDirectMemorySize set by the VM.
    // The system property will be removed.
    String s = (String)props.remove("sun.nio.MaxDirectMemorySize");
    if (s != null) {
        if (s.equals("-1")) {
            // -XX:MaxDirectMemorySize not given, take default
            directMemory = Runtime.getRuntime().maxMemory();
        } else {
            long l = Long.parseLong(s);
            if (l > -1)
                directMemory = l;
        }
    }

    //...
}

所以默認(rèn)情況下,可以申請的DirectByteBuffer大小為Runtime.getRuntime().maxMemory(),而這個值等于可用的最大Java堆大小,也就是我們-Xmx參數(shù)指定的值。

所以最終結(jié)論是:默認(rèn)情況下,可以申請的最大DirectByteBuffer空間為Java最大堆大小的值。

和DirectByteBuffer有關(guān)的JVM選項

根據(jù)上文的分析,有兩個JVM參數(shù)與DirectByteBuffer直接相關(guān):

  • -XX:+PageAlignDirectMemory:指定申請的內(nèi)存是否需要按頁對齊,默認(rèn)不對其
  • -XX:MaxDirectMemorySize=<size>,可以申請的最大DirectByteBuffer大小,默認(rèn)與-Xmx相等

參考資料

  • Java Max Direct Memory Size設(shè)置 – CSDN博客
  • Runtime.getRunTime.maxMemory為啥比Xmx指定的內(nèi)存小 – CSDN博客
  • JVM源碼分析之堆外內(nèi)存完全解讀 – 你假笨

標(biāo)簽: 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:Map大家族的那點事兒(4) :HashMap

下一篇:Java 線程池詳解