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

JDK 源碼閱讀 : DirectByteBuffer

2018-09-12    來(lái)源:importnew

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

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

如何使用DirectByteBuffer

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

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

DirectByteBuffer實(shí)例化流程

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

DirectByteBuffer(int cap) {                   // package-private
	// 初始化Buffer的四個(gè)核心屬性
    super(-1, 0, cap, cap);
    // 判斷是否需要頁(yè)面對(duì)齊,通過(guò)參數(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ī)制注冊(cè)內(nèi)存回收處理函數(shù)
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

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

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

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

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

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

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

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

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

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

// 申請(qǐng)一塊本地內(nèi)存。內(nèi)存空間是未初始化的,其內(nèi)容是無(wú)法預(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申請(qǐng)內(nèi)存,內(nèi)部使用malloc函數(shù)申請(qǐng)內(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這個(gè)C標(biāo)準(zhǔn)庫(kù)的函數(shù)來(lái)申請(qǐng)內(nèi)存。

DirectByteBuffer回收流程

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

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

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

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

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

DirectByteBuffer就是使用Cleaner機(jī)制來(lái)實(shí)現(xiàn)本身被GC時(shí),回收堆外內(nèi)存的能力。我們來(lái)看一下其回收處理函數(shù)是如何實(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)計(jì)變量
            Bits.unreserveMemory(size, capacity);
        }

    }

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

DirectByteBuffer讀寫(xiě)邏輯

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)這兩個(gè)方法來(lái)讀寫(xiě)堆外內(nèi)存空間的指定位置的字節(jié)數(shù)據(jù)。不過(guò)這兩個(gè)方法本地實(shí)現(xiàn)比較復(fù)雜,這里就不分析了。

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

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

DirectByteBuffer通過(guò)sun.misc.VM#maxDirectMemory來(lái)獲取這個(gè)值,可以看一下對(duì)應(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,那對(duì)外內(nèi)存的默認(rèn)大小是64MB嗎?不是,仔細(xì)看注釋?zhuān)⑨屩姓f(shuō),這個(gè)值會(huì)在JRE啟動(dòng)過(guò)程中被重新設(shè)置為用戶(hù)指定的值,如果用戶(hù)沒(méi)有指定,則會(huì)設(shè)置為Runtime.getRuntime().maxMemory()。

這個(gè)過(guò)程發(fā)生在sun.misc.VM#saveAndRemoveProperties函數(shù)中,這個(gè)函數(shù)會(huì)被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)情況下,可以申請(qǐng)的DirectByteBuffer大小為Runtime.getRuntime().maxMemory(),而這個(gè)值等于可用的最大Java堆大小,也就是我們-Xmx參數(shù)指定的值。

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

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

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

  • -XX:+PageAlignDirectMemory:指定申請(qǐng)的內(nèi)存是否需要按頁(yè)對(duì)齊,默認(rèn)不對(duì)其
  • -XX:MaxDirectMemorySize=<size>,可以申請(qǐng)的最大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),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

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

下一篇:Java 線程池詳解