DIY主題討論11:對象引用類型

【第11次討論主題:對象引用類型】

用自己接觸的業務場景或自己的理解角度說明軟引用和弱引用的價值(若有代碼可附上)

概念理解

在jvm中主流的垃圾回收通過對象的可達性分析來確定對象是否回收,有四種飲用類型:強引用、弱引用、軟引用、虛引用

  1. 強引用:JVM中默認的對象引用方式,通過強引用指向對象,只要有強引用存在,就可以表明對象還活着。任何強引用對象都不能被垃圾回收,當內存空間不足,Java虛擬機會拋出OutOfMemoryError錯誤,使程序異常終止,所以在使用強引用的時候,需要注意對象的大小生命週期防止發生OOM,尤其是使用一些Map,List之類的成員變量時更要注意。
  2. 軟引用(SoftReference):軟引用是一種相對強引用弱化的引用,可以讓對象豁免一些垃圾收集,直到JVM認爲內存不足時才進行回收。
  3. 弱引用(WeakReference):弱引用需要用 WeakReference 類來實現,它比軟引用的生存期更短,對於只有弱引用的對象來說,只要垃圾回收機制一運行,不管 JVM 的內存空間是否足夠,總會回收該對象佔用的內存.但是通過 ReferenceQueue 隊列,可以再次獲取數據信息
  4. 虛引用(PhantomReference):不能通過虛引用訪問對象,虛引用僅提供一種確保對象被finalize以後,做某些事情的機制。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。

價值

  1. 強引用價值:強引用保障了應用程序中正在使用的對象,不會被垃圾回收器意外回收,爲應用程序穩定運行提供了保障,但是當服務器內存資源耗盡,會發生OOM導致系統終止。

  2. 軟引用價值:軟引用區別於強引用,系統內存充足時,對象不會被垃圾回收,但是在要發生OOM前,軟引用對象會被回收,軟引用適合用在內存大小敏感的緩存.如果還有空閒內存,就可以暫時保留緩存,當內存不足時清理掉,保障使用緩存的同時不會耗盡內存。 比如用LRU實現定長內存緩存.

  3. 弱引用價值:
    根據概念描述,比較適合的場景更傾向於對偶爾使用的數據做存儲,隨取隨有.隨用隨銷.
    可以用來構建一種沒有特定約束的關係,比如,維護一種非強制性的映射關 系,如果試圖獲取時對象還在,就使用它,否則重現實例化。它同樣是很多緩存實現的選擇。

  4. 虛引用價值
    通常用來做所謂的 Post-Mortem 清理機 制,我在專欄上一講中介紹的 Java 平臺自身 Cleaner 機制等,也有人利用幻象引用監控對象的 創建和銷燬。
    虛引用主要用來跟蹤對象被垃圾回收器回收的活動。一般可以通過虛引用達到回收一些非java內的一些資源比如堆外內存的行爲。
    對象finalize會嚴重影響JVM垃圾回收性能,在jdk9已被廢棄,不推薦使用。推薦使用java.lang.ref.Cleaner,Cleaner也利用了虛引用。https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html

應用場景

軟引用

項目裏找了找SoftReference的應用,除了jvm裏,主要是fastjson中有。
ThreadLocalCache,其作用是避免轉換時分配char[]的開銷。
在線程全局變量中緩存初始化的char數組,數組緩存存在解決分配數組的開銷,內存不足清理數組,使用時再重新分配,不會對程序運行產生其他影響。

private final static ThreadLocal<SoftReference<char[]>> charsBufLocal = new ThreadLocal<SoftReference<char[]>>();
public static void clearChars() {
	charsBufLocal.set(null);
}
public static char[] getChars(int length) {
	SoftReference<char[]> ref = charsBufLocal.get();
	// ref == null有可能是被當作垃圾清理了,這時需要重新獲取
    if (ref == null) {
       return allocate(length);
    }
    char[] chars = ref.get();
    if (chars == null) {
       return allocate(length);
    }
    if (chars.length < length) {
       chars = allocate(length);
    }
    return chars;
}
private static char[] allocate(int length) {
    if(length> CHARS_CACH_MAX_SIZE) {
        return new char[length];
    }
    int allocateLength = getAllocateLengthExp(CHARS_CACH_INIT_SIZE_EXP, CHARS_CACH_MAX_SIZE_EXP, length);
    char[] chars = new char[allocateLength];
    charsBufLocal.set(new SoftReference<char[]>(chars));
    return chars;
}

弱引用

Eureka中StringCache的使用,先了解java中String#intern(…),該方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中。StringCache和核心也是intern,可以類比理解StringCache的意義。

  • JDK 自帶的 String Pool常量池 固定大小( jvm:-XX:StringTableSize配置),不支持自動擴容,大量使用 String#intern(…) 後,會導致性能大幅度下降。
  • Eureka 的應用實例( InstanceInfo ) 的 appName、appGroupName、vipAddress、secureVipAddress、metadata 和應用( Application )的 name 等屬性需要使用到 String Pool ,爲了在大量的網絡通信序列化反序列的過程中,速度更快,更節省內容。

緩存中存在則設置讀鎖獲取,如果沒有則緩存設置進去,引入緩存優化性能,隨GC清除不會佔用內存影響程序運行。

public class StringCache {
    public static final int LENGTH_LIMIT = 38;
    private static final StringCache INSTANCE = new StringCache();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Map<String, WeakReference<String>> cache = new WeakHashMap<String, WeakReference<String>>();
    private final int lengthLimit;
    public StringCache() {
        this(LENGTH_LIMIT);
    }
    public StringCache(int lengthLimit) {
        this.lengthLimit = lengthLimit;
    }
    public String cachedValueOf(final String str) {
        if (str != null && (lengthLimit < 0 || str.length() <= lengthLimit)) {
            // Return value from cache if available
            try {
                lock.readLock().lock();
                WeakReference<String> ref = cache.get(str);
                if (ref != null) {
                    return ref.get();
                }
            } finally {
                lock.readLock().unlock();
            }

            // Update cache with new content
            try {
                lock.writeLock().lock();
                WeakReference<String> ref = cache.get(str);
                if (ref != null) {
                    return ref.get();
                }
                cache.put(str, new WeakReference<>(str));
            } finally {
                lock.writeLock().unlock();
            }
            return str;
        }
        return str;
    }
    public int size() {
        try {
            lock.readLock().lock();
            return cache.size();
        } finally {
            lock.readLock().unlock();
        }
    }
    public static String intern(String original) {
        return INSTANCE.cachedValueOf(original);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章