强引用和 LRUCache

一 点睛

强引用(Strong Reference)是我们使用最多的一种对象引用,当一个对象被关键字 new 实例化出来的时候, JVM 会在堆(heap)内存中开辟一个内存区域,用于存放与该实例对应的数据结构。JVM 垃圾回收器线程会在达到 GC 条件的时候尝试回收(Full GC,Young GC)堆栈内存中的数据,强引用的特点是只要引用到 Root 根的路径可达,无论怎样的 GC 都不会将其释放,而是宁可出现 JVM 内存溢出。

Cache 是一种用于提高系统性能,提高数据检索效率的机制,而 LRU(Least recently used,最近最少使用)算法和 Cache 的结合是最常见的一种 Cache 实现。

LRU 是数据冷热治理的一种思想,不经常使用的数据被称为冷数据,经常使用的则被称为热数据,对冷数据分配提前释放,可以帮助我们节省更多的内存资源,LRUCache 的实现方式有很多种,在这里使用双向链表+hash表的方式来实现。

二 实战

1 定义被引用的对象

package concurrent.lrucache.strongRefercence;

/**
* @className: Reference
* @description: 当 Reference 对象被实例化后,会在堆内存中创建 1M 的内存空间
* @date: 2022/4/28
* @author: cakin
*/
public class Reference {
    // 1M
    private final byte[] data = new byte[2 << 19];

    /**
     * 功能描述:会在垃圾回收的标记阶段被调用,垃圾回收器在回收一个对象之前,首先会进行标记,标记的过程会调用该对象的 finalize 方法
     * 所以千万不要认为该方法被调用之后,就代表对象已被垃圾回收器回收,对象在 finalize 方法中是可以”自我救赎“的。
     *
     * @author cakin
     * @date 2022/4/28
     * @description:
     */
    @Override
    protected void finalize() throws Throwable {
        System.out.println("the reference will be GC");
    }
}

2 最近最少使用缓存

package concurrent.lrucache.strongRefercence;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

/**
* @className: LRUCache
* @description: 最近最少使用缓存
* @date: 2022/4/28
* @author: cakin
*/
public class LRUCache<K, V> {
    // 用于记录 key 值的顺序
    private final LinkedList<K> keyList = new LinkedList<>();
    // 用于存放数据
    private final Map<K, V> cache = new HashMap<>();
    // cache 的最大容量
    private final int capacity;

    // 提供了一种加载数据的方式
    private final CacheLoader<K, V> cacheLoader;

    public LRUCache(int capacity, CacheLoader<K, V> cacheLoader) {
        this.capacity = capacity;
        this.cacheLoader = cacheLoader;
    }

    public void put(K key, V value) {
        // 当元素数量超过容量时,将最老的数据清除
        if (keyList.size() >= capacity) {
            K eldestkey = keyList.removeFirst();  // eldest data
            cache.remove(eldestkey);
        }
        // 如果数据已经存在,则从 key 的队列中删除
        if (keyList.contains(key)) {
            keyList.remove(key);
        }
        // 将 key 存放到队尾
        keyList.addLast(key);
        cache.put(key, value);
    }

    public V get(K key) {
        V value;
        // 先将 key 从 key list 中删除
        boolean success = keyList.remove(key);
        // 如果删除失败则表明该数据不存在
        if (!success) {
            // 通过 cacheLoader 对数据进行加载
            value = cacheLoader.load(key);
            // 通过 put 方法 cache 数据
            this.put(key, value);
        } else {
            // 如果删除成功,则从 cache 中返回数据,并且将 key 再次放到队尾
            value = cache.get(key);
            keyList.addLast(key);
        }
        return value;
    }

    @Override
    public String toString() {
        return this.keyList.toString();
    }
}

3 数据加载

package concurrent.lrucache.strongRefercence;

@FunctionalInterface
public interface CacheLoader<K, V> {
    // 定义加载数据的方法
    V load(K k);
}

4 测试

package concurrent.lrucache.strongRefercence;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        test1();
        test2();
    }

    private static void test1() {
        LRUCache<String, Reference> cache = new LRUCache<>(5, key -> new Reference());
        cache.get("1");
        cache.get("2");
        cache.get("3");
        cache.get("4");
        cache.get("5");
        cache.get("6");
        System.out.println(cache.toString());
    }

    private static void test2() {
        LRUCache<Integer, Reference> cache1 = new LRUCache<>(200, key -> new Reference());
        for (Integer i = 0; i < Integer.MAX_VALUE; i++) {
            cache1.get(i);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("The " + i + " reference stored at cache.");
        }
    }
}

三 test1 测试

1 测试结果

[2, 3, 4, 5, 6]

2 测试说明

取缓存时,把最老的数据 1 给踢掉

四 test2 测试结果

1 启动参数

-Xmx128M -Xms64M -XX:+PrintGCDetails

2 测试结果

......

The 93 reference stored at cache.

The 94 reference stored at cache.

The 95 reference stored at cache.

The 96 reference stored at cache.

The 97 reference stored at cache.

The 98 reference stored at cache.

[Full GC (Ergonomics) [PSYoungGen: 15677K->15362K(18944K)] [ParOldGen: 87227K->87226K(87552K)] 102904K->102589K(106496K), [Metaspace: 4683K->4683K(1056768K)], 0.0071742 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

[Full GC (Allocation Failure) [PSYoungGen: 15362K->15362K(18944K)] [ParOldGen: 87226K->87226K(87552K)] 102589K->102589K(106496K), [Metaspace: 4683K->4683K(1056768K)], 0.0028789 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Heap

PSYoungGen      total 18944K, used 16002K [0x00000000fd580000, 0x0000000100000000, 0x0000000100000000)

  eden space 16384K, 97% used [0x00000000fd580000,0x00000000fe520860,0x00000000fe580000)

  from space 2560K, 0% used [0x00000000fe580000,0x00000000fe580000,0x00000000fe800000)

  to   space 14336K, 0% used [0x00000000ff200000,0x00000000ff200000,0x0000000100000000)

ParOldGen       total 87552K, used 87226K [0x00000000f8000000, 0x00000000fd580000, 0x00000000fd580000)

  object space 87552K, 99% used [0x00000000f8000000,0x00000000fd52ea90,0x00000000fd580000)

Metaspace       used 4715K, capacity 4882K, committed 4992K, reserved 1056768K

  class space    used 523K, capacity 559K, committed 640K, reserved 1048576K

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

    at concurrent.lrucache.strongRefercence.Reference.<init>(Reference.java:11)

    at concurrent.lrucache.strongRefercence.Test.lambda$test2$1(Test.java:23)

    at concurrent.lrucache.strongRefercence.Test$$Lambda$1/159413332.load(Unknown Source)

    at concurrent.lrucache.strongRefercence.LRUCache.get(LRUCache.java:51)

    at concurrent.lrucache.strongRefercence.Test.test2(Test.java:25)

    at concurrent.lrucache.strongRefercence.Test.main(Test.java:8)

3 测试说明

强引用产生了内存溢出问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值