JAVA基礎(二)內存優化-使用Java引用作緩存

Java極客  |  做者  /  鏗然一葉
這是Java極客的第 53 篇原創文章

相關閱讀:java

JAVA基礎(一)簡單、透徹理解內部類和靜態內部類
JAVA基礎(三)ClassLoader實現熱加載
JAVA基礎(四)枚舉(enum)和常量定義,工廠類使用對比
JAVA基礎(五)函數式接口-複用,解耦之利刃
JAVA編程思想(一)經過依賴注入增長擴展性
JAVA編程思想(二)如何面向接口編程
JAVA編程思想(三)去掉彆扭的if,自注冊策略模式優雅知足開閉原則
JAVA編程思想(四)Builder模式經典範式以及和工廠模式如何選?
HikariPool源碼(二)設計思想借鑑
人在職場(一)IT大廠生存法則算法


1. 引用類型

Java中引用類型有如下幾類:數據庫

類型 描述
強引用 對象具備強引用,不會被垃圾回收,即便發生OutOfMemoryError。
軟引用 對象具備軟引用,在內存空間足夠時,垃圾回收器不會回收它;當內存空間不足時,就會回收這些對象。
弱引用 對象具備弱引用,垃圾回收時若是發現了它就會被回收,而無論內存是否足夠。
虛引用 對象具備弱引用,在任什麼時候候均可能被垃圾回收器回收。

根據軟引用和弱引用的特色,他們適合作內存敏感應用的緩存,當內存不足時會被回收,同時須要注意的是這些緩存是否高頻訪問,若是緩存不可用,會不會致使數據庫壓力過大而掛掉,若是會則還要考慮熔斷,降級等處理。編程

2. 強引用

JAVA中,通常對象的定義都是強引用。顯式地設置強引用對象爲null,或超出對象的生命週期範圍,則gc認爲該對象不存在引用,這時就能夠回收這個對象,具體何時收集這要取決於gc的算法。緩存

3. 軟引用緩存例子

這個例子的目的以下:dom

1. 軟引用作緩存的通常用法。
2. 驗證在垃圾收集後會回收軟引用對象。ide

3.1 代碼

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

public class SoftReferenceDemo {
    public static void main(String[] args) {
        OrderManager orderManager = new OrderManager();

        // 獲取訂單線程,不斷獲取訂單,驗證系統內存不足後緩存數據會被清理掉,從新從數據庫獲取。
        new Thread(()->{
            while (true) {
                orderManager.getOrder("101");
                quietlySleep(2000);
            }
        }).start();

        // 不斷建立新對象,模擬內存不足致使垃圾回收,新對象也是軟引用,這樣能夠被回收,避免OOM異常。
        new Thread(()->{
            List<SoftReference<BigObject>> list = new ArrayList<>();
            while (true) {
                list.add(new SoftReference<>(new BigObject()));
                quietlySleep(50);
            }
        }).start();

        // 主線程休眠等待
        quietlySleep(20 * 60 * 1000);
    }

    private static void quietlySleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    // 模擬大對象
    private static class BigObject {
        byte[] b = new byte[4 * 1024];
    }
}

class OrderManager {
    public Order getOrder(String id) {
        Order order = OrderCache.getInstance().getCachedOrder(id);
        if (order == null) {
            order = getOrderFromDB(id);
        }
        return order;
    }

    private Order getOrderFromDB(String id) {
        Order order = new Order(id, (int) (Math.random() * 100));
        System.out.println(new Date() + " get order from DB. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX " + order);
        OrderCache.getInstance().cache(order);
        return order;
    }
}

class OrderCache {
    private static volatile OrderCache singlonCache;
    private HashMap<String, OrderReference> refChache;
    private ReferenceQueue queue;

    private OrderCache(){
        this.queue=new ReferenceQueue();
        this.refChache=new HashMap<>();
    }

    public static OrderCache getInstance(){
        if(singlonCache == null){
            synchronized (OrderCache.class) {
                if (singlonCache == null) {
                    singlonCache=new OrderCache();
                }
            }
        }
        return singlonCache;
    }

    public void cache(Order order){
        cleanCache();//清除已經標記爲垃圾的引用
        OrderReference reference = new OrderReference(order, queue);
        refChache.put(order.getId(), reference);//將對象的軟引用保存到緩存中
    }

    public Order getCachedOrder(String key){
        Order order = null;
        if (refChache.containsKey(key)){
            order= (Order) refChache.get(key).get();
            System.out.println(new Date() + " get order from cache. " + order);
        }
        return order;
    }

    private void cleanCache(){
        OrderReference reference = null;
        while ((reference = (OrderReference)queue.poll()) != null){
            System.out.println(new Date() + " cleanCache");
            refChache.remove(reference._key);
        }
    }

    static class OrderReference extends SoftReference {
        private String _key;
        public OrderReference(Order referent, ReferenceQueue q) {
            super(referent, q);
            _key = referent.getId();
        }
    }
}

class Order {
    private String id;
    private long price;
    public Order(String id, long price) {
        this.id = id;
        this.price = price;
    }

    public String getId() {
        return id;
    }

    @Override
    public String toString() {
        return "order id: " + id + ", price: " + price;
    }
}
複製代碼

3.2 調整啓動參數

調整啓動參數內存大小,使得更容易知足內存不足場景。函數

3.3 運行輸出

Sun Apr 05 17:46:18 GMT+08:00 2020 get order from DB. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX order id: 101, price: 21
Sun Apr 05 17:46:20 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:22 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:24 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:26 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:28 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:30 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:32 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:34 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:36 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:38 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:40 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:42 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:44 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:46 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:48 GMT+08:00 2020 get order from cache. order id: 101, price: 21
Sun Apr 05 17:46:50 GMT+08:00 2020 get order from cache. null
Sun Apr 05 17:46:50 GMT+08:00 2020 get order from DB. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX order id: 101, price: 87
Sun Apr 05 17:46:50 GMT+08:00 2020 cleanCache
Sun Apr 05 17:46:52 GMT+08:00 2020 get order from cache. order id: 101, price: 87
Sun Apr 05 17:46:54 GMT+08:00 2020 get order from cache. order id: 101, price: 87
複製代碼

能夠看到當JVM內存不足,作垃圾回收後軟引用會被回收,此時從緩存中沒法得到數據,會從新從DB中獲取數據。post

4. 弱引用例子

4.1 代碼

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

public class WeakReferenceDemo {
    public static void main(String args[]) {
        final int size = 3;
        List<WeakReference<DBConnection>> weakList = new ArrayList<WeakReference<DBConnection>>();
        for (int i = 0; i < size; i++) {
            DBConnection dbConnection = new DBConnection("DBConnection-" + i);
            weakList.add(new WeakReference(dbConnection));
            System.out.println(dbConnection + " be created.");
        }
        checkDBConnection(weakList);
        quietlySleep(20000); // 休眠時間調整到你有足夠時間在gc以前輸入命令 jmap -histo:live <pid> >beforegc.txt,並能在gc以前完成信息收集
        System.gc();                   // 不要經過在這裏打斷點來執行jmap命令,當暫停到斷點時,jmap命令也會暫停執行,斷點恢復後,會分不清jsmp收集的是GC前仍是GC後的信息
        System.out.println("gc be called.");
        quietlySleep(1000); // 這裏佔用內存少,很快就回收了,佔用內存大的就多給點時間
        checkDBConnection(weakList);   // 這裏能夠打個斷點,以讓你知道能夠輸入命令 jmap -histo:live <pid> >aftergc.txt
        quietlySleep(1000); // 休眠時間調整到在程序退出前有足夠時間完成信息收集
    }

    // 檢查DBConnection是否被垃圾回收
    private static void checkDBConnection(List<WeakReference<DBConnection>> weakList) {
        for (WeakReference<DBConnection> ref: weakList) {
            DBConnection dbConnection = ref.get();
            System.out.println("dbConnection is null ? " + (dbConnection == null));
        }
    }

    // 讓我安靜睡會
    private static void quietlySleep(long timeMillis) {
        try {
            Thread.sleep(timeMillis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 模擬數據庫鏈接資源
class DBConnection {
    public String id;

    public DBConnection(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    @Override
    public String toString() {
        return id;
    }
}
複製代碼

4.2 運行後輸出日誌

DBConnection-0 be created.
DBConnection-1 be created.
DBConnection-2 be created.
dbConnection is null ? false
dbConnection is null ? false
dbConnection is null ? false
gc be called.
dbConnection is null ? true
dbConnection is null ? true
dbConnection is null ? true
複製代碼

按照代碼中的說明經過jmap命令驗證對象是否被回收,在gc以前執行:ui

jmap -histo 21636 >a.txt
複製代碼

注意修改進程號,可經過如下命令查看進程號:

jps |findstr WeakReferenceDemo
複製代碼

在GC以後執行:

jmap -histo 21636 >b.txt
複製代碼

打開a.txt和b.txt文件查找DBConnection對象,只能在a.txt中找到,而b.txt中找步到,說明確實被回收了。

4.3 其餘例子

在HikariPool中也使用弱引用作緩存,參考HikariPool源碼(二)設計思想借鑑

5. 虛引用

沒有找到合適的例子和用法。

6. 總結

  1. 在內存敏感的應用中能夠使用軟引用和弱引用來作緩存,可用根據場景和重要性使用強引用,軟引用,弱引用。
  2. 須要考慮緩存不可用時對系統的影響,例如數據庫壓力增大,作好熔斷,降級等措施。

end.


<--感謝三連擊,左邊點贊和關注。

相關文章
相關標籤/搜索