堆外內(nèi)存和虛引用

堆外內(nèi)存

概念

堆內(nèi)內(nèi)存在 Java 中對象都是在堆內(nèi)分配的,通常我們說的JVM 內(nèi)存也就指的堆內(nèi)內(nèi)存,堆內(nèi)內(nèi)存完全被JVM 虛擬機(jī)所管理,JVM 有自己的垃圾回收算法,對于使用者來說不必關(guān)心對象的內(nèi)存如何回收。
堆外內(nèi)存堆外內(nèi)存與堆內(nèi)內(nèi)存相對應(yīng),對于整個機(jī)器內(nèi)存而言,除堆內(nèi)內(nèi)存以外部分即為堆外內(nèi)存,如下圖所示。堆外內(nèi)存不受 JVM 虛擬機(jī)管理,直接由操作系統(tǒng)管理。

堆內(nèi)堆外優(yōu)劣

1.堆內(nèi)內(nèi)存由 JVM GC 自動回收內(nèi)存,降低了 Java 用戶的使用心智,但是 GC 是需要時間開銷成本的,堆外內(nèi)存由于不受 JVM 管理,所以在一定程度上可以降低 GC 對應(yīng)用運(yùn)行時帶來的影響。
2.堆外內(nèi)存需要手動釋放,這一點(diǎn)跟 C/C++ 很像,稍有不慎就會造成應(yīng)用程序內(nèi)存泄漏,當(dāng)出現(xiàn)內(nèi)存泄漏問題時排查起來會相對困難。
3.當(dāng)進(jìn)行網(wǎng)絡(luò) I/O 操作、文件讀寫時,堆內(nèi)內(nèi)存都需要轉(zhuǎn)換為堆外內(nèi)存,然后再與底層設(shè)備進(jìn)行交互,這一點(diǎn)在介紹 writeAndFlush 的工作原理中也有提到,所以直接使用堆外內(nèi)存可以減少一次內(nèi)存拷貝。
4.堆外內(nèi)存可以實(shí)現(xiàn)進(jìn)程之間、JVM 多實(shí)例之間的數(shù)據(jù)共享。

堆外內(nèi)存分配

ByteBuffer#allocateDirect
// 分配 10M 堆外內(nèi)存
ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024); 

DirectByteBuffer(int cap) {
    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)) {
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

在堆內(nèi)存放的 DirectByteBuffer 對象并不大,僅僅包含堆外內(nèi)存的地址、大小等屬性,同時還會創(chuàng)建對應(yīng)的 Cleaner 對象,通過 ByteBuffer 分配的堆外內(nèi)存不需要手動回收,它可以被 JVM 自動回收。當(dāng)堆內(nèi)的 DirectByteBuffer 對象被 GC 回收時,Cleaner 就會用于回收對應(yīng)的堆外內(nèi)存。


堆外內(nèi)存.png
Unsafe#allocateMemory

Unsafe 是一個非常不安全的類,它用于執(zhí)行內(nèi)存訪問、分配、修改等敏感操作,可以越過 JVM 限制的枷鎖。在 Java 中是不能直接使用 Unsafe 的,但是我們可以通過反射獲取 Unsafe 實(shí)例。獲得 Unsafe 實(shí)例后,我們可以通過 allocateMemory 方法分配堆外內(nèi)存,allocateMemory 方法返回的是內(nèi)存地址

// 分配 10M 堆外內(nèi)存
long address = unsafe.allocateMemory(10 * 1024 * 1024);

與 DirectByteBuffer 不同的是,Unsafe#allocateMemory 所分配的內(nèi)存必須自己手動釋放,否則會造成內(nèi)存泄漏,這也是 Unsafe 不安全的體現(xiàn)。Unsafe 同樣提供了內(nèi)存釋放的操作:

unsafe.freeMemory(address);

堆外內(nèi)存回收

Java 對象有四種引用方式:強(qiáng)引用 StrongReference、軟引用 SoftReference、弱引用 WeakReference 和虛引用 PhantomReference。其中 PhantomReference 是最不常用的一種引用方式,Cleaner 就屬于 PhantomReference 的子類,如以下源碼所示,PhantomReference 不能被單獨(dú)使用,需要與引用隊列 ReferenceQueue 聯(lián)合使用。

public class Cleaner extends java.lang.ref.PhantomReference<java.lang.Object> {
    private static final java.lang.ref.ReferenceQueue<java.lang.Object> dummyQueue;
    private static sun.misc.Cleaner first;
    private sun.misc.Cleaner next;
    private sun.misc.Cleaner prev;
    private final java.lang.Runnable thunk;
    public void clean() {}
}

初始化堆外內(nèi)存時,Cleaner 對象在初始化時會加入 Cleaner 鏈表中。DirectByteBuffer 對象包含堆外內(nèi)存的地址、大小以及 Cleaner 對象的引用,ReferenceQueue 用于保存需要回收的 Cleaner 對象。


堆外內(nèi)存回收.png

當(dāng)發(fā)生 GC 時,DirectByteBuffer 對象被回收,此時 Cleaner 對象不再有任何引用關(guān)系,在下一次 GC 時,該 Cleaner 對象將被添加到 ReferenceQueue 中,并執(zhí)行 clean() 方法。clean() 方法中將 Cleaner 對象從 Cleaner 鏈表中移除,同時調(diào)用 unsafe.freeMemory 方法清理堆外內(nèi)存。

private Cleaner(Object var1, Runnable var2) {
    super(var1, dummyQueue);
    this.thunk = var2;
}

public void clean() {
    if (remove(this)) {
        try {
            this.thunk.run();
        } catch (final Throwable var2) {
            ......
        }

    }
}

小結(jié)

堆外內(nèi)存是一把雙刃劍,在網(wǎng)絡(luò) I/O、文件讀寫、分布式緩存等領(lǐng)域使用堆外內(nèi)存都更加簡單、高效,此外使用堆外內(nèi)存不受 JVM 約束,可以避免 JVM GC 的壓力,降低對業(yè)務(wù)應(yīng)用的影響。當(dāng)然天下沒有免費(fèi)的午餐,堆外內(nèi)存也不能濫用,使用堆外內(nèi)存你就需要關(guān)注內(nèi)存回收問題,雖然 JVM 在一定程度上幫助我們實(shí)現(xiàn)了堆外內(nèi)存的自動回收,但我們?nèi)匀恍枰囵B(yǎng)類似 C/C++ 的分配/回收的意識,出現(xiàn)內(nèi)存泄漏問題能夠知道如何分析和處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容