强引用和 LRUCache

x33g5p2x  于2022-05-05 转载在 其他  
字(4.9k)|赞(0)|评价(0)|浏览(448)

一 点睛

强引用(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 定义被引用的对象

  1. package concurrent.lrucache.strongRefercence;
  2. /**
  3. * @className: Reference
  4. * @description: 当 Reference 对象被实例化后,会在堆内存中创建 1M 的内存空间
  5. * @date: 2022/4/28
  6. * @author: cakin
  7. */
  8. public class Reference {
  9. // 1M
  10. private final byte[] data = new byte[2 << 19];
  11. /**
  12. * 功能描述:会在垃圾回收的标记阶段被调用,垃圾回收器在回收一个对象之前,首先会进行标记,标记的过程会调用该对象的 finalize 方法
  13. * 所以千万不要认为该方法被调用之后,就代表对象已被垃圾回收器回收,对象在 finalize 方法中是可以”自我救赎“的。
  14. *
  15. * @author cakin
  16. * @date 2022/4/28
  17. * @description:
  18. */
  19. @Override
  20. protected void finalize() throws Throwable {
  21. System.out.println("the reference will be GC");
  22. }
  23. }

2 最近最少使用缓存

  1. package concurrent.lrucache.strongRefercence;
  2. import java.util.HashMap;
  3. import java.util.LinkedList;
  4. import java.util.Map;
  5. /**
  6. * @className: LRUCache
  7. * @description: 最近最少使用缓存
  8. * @date: 2022/4/28
  9. * @author: cakin
  10. */
  11. public class LRUCache<K, V> {
  12. // 用于记录 key 值的顺序
  13. private final LinkedList<K> keyList = new LinkedList<>();
  14. // 用于存放数据
  15. private final Map<K, V> cache = new HashMap<>();
  16. // cache 的最大容量
  17. private final int capacity;
  18. // 提供了一种加载数据的方式
  19. private final CacheLoader<K, V> cacheLoader;
  20. public LRUCache(int capacity, CacheLoader<K, V> cacheLoader) {
  21. this.capacity = capacity;
  22. this.cacheLoader = cacheLoader;
  23. }
  24. public void put(K key, V value) {
  25. // 当元素数量超过容量时,将最老的数据清除
  26. if (keyList.size() >= capacity) {
  27. K eldestkey = keyList.removeFirst(); // eldest data
  28. cache.remove(eldestkey);
  29. }
  30. // 如果数据已经存在,则从 key 的队列中删除
  31. if (keyList.contains(key)) {
  32. keyList.remove(key);
  33. }
  34. // 将 key 存放到队尾
  35. keyList.addLast(key);
  36. cache.put(key, value);
  37. }
  38. public V get(K key) {
  39. V value;
  40. // 先将 key 从 key list 中删除
  41. boolean success = keyList.remove(key);
  42. // 如果删除失败则表明该数据不存在
  43. if (!success) {
  44. // 通过 cacheLoader 对数据进行加载
  45. value = cacheLoader.load(key);
  46. // 通过 put 方法 cache 数据
  47. this.put(key, value);
  48. } else {
  49. // 如果删除成功,则从 cache 中返回数据,并且将 key 再次放到队尾
  50. value = cache.get(key);
  51. keyList.addLast(key);
  52. }
  53. return value;
  54. }
  55. @Override
  56. public String toString() {
  57. return this.keyList.toString();
  58. }
  59. }

3 数据加载

  1. package concurrent.lrucache.strongRefercence;
  2. @FunctionalInterface
  3. public interface CacheLoader<K, V> {
  4. // 定义加载数据的方法
  5. V load(K k);
  6. }

4 测试

  1. package concurrent.lrucache.strongRefercence;
  2. import java.util.concurrent.TimeUnit;
  3. public class Test {
  4. public static void main(String[] args) {
  5. test1();
  6. test2();
  7. }
  8. private static void test1() {
  9. LRUCache<String, Reference> cache = new LRUCache<>(5, key -> new Reference());
  10. cache.get("1");
  11. cache.get("2");
  12. cache.get("3");
  13. cache.get("4");
  14. cache.get("5");
  15. cache.get("6");
  16. System.out.println(cache.toString());
  17. }
  18. private static void test2() {
  19. LRUCache<Integer, Reference> cache1 = new LRUCache<>(200, key -> new Reference());
  20. for (Integer i = 0; i < Integer.MAX_VALUE; i++) {
  21. cache1.get(i);
  22. try {
  23. TimeUnit.SECONDS.sleep(1);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println("The " + i + " reference stored at cache.");
  28. }
  29. }
  30. }

三 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 测试说明

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

相关文章