Java 高并发编程详解 15.0 java引用类型和GC回收机制

本文介绍了Java中引用类型的区分,包括强引用、软引用、弱引用和虚引用,详细阐述了它们在内存管理和垃圾回收中的作用。通过LRUCache的案例,展示了不同引用类型在缓存治理中的应用,并通过实际测试验证了各种引用类型的GC行为,特别是对内存限制下的资源回收效果。
摘要由CSDN通过智能技术生成

为什么出现引用类型的区分

因为当执行完成任务后程序需要回收一些资源型对象,例如数据库连接Connection,Socket,IO流资源等对象时,使用try-finally方式或者hook钩子子线程来关闭资源型对象不一定保证其对象会被GC回收机制处理掉,所以用引用类型来保证对象一定会被GC回收掉。

四种引用类型

  1. 强引用:java程序中常见的引用,把一个对象赋值给一个引用变量时,该引用变量就是强引用。当对象被强引用变量引用时,该对象是不能被GC回收,是造成内存泄漏的主要原因。
  2. 软引用:需要声明SoftReference来表明对象的引用变量类型是软引用。该对象在内存足够时不会被GC回收,而当发现内存不足时GC会直接回收,存放在对应的ReferenceQueue队列中。
  3. 弱引用:需要声明WeakReference来表明对象的引用变量类型是弱引用。该对象不管内存是否足够,都会在GC运行时回收,存放在对应的ReferenceQueue队列中。
  4. 虚引用:需要声明PhantomReference来来实现,构造方法中包含ReferenceQueue(引用队列)。用来触发对象的再次操作。

LRUCache(冷热数据缓存治理)的方式测试引用类型

  1. 自定义内存大小为1M的引用对象类

/**
 * 引用对象类 设置1M的内存
 */
public class Reference {
    // 1M
    private final byte[] data = new byte[2 << 19];

    @Override
    protected void finalize() throws Throwable {
        System.out.println("GC回收机制触发,回收对象");
    }
}
  1. 定义函数式接口 逻辑执行单元
@FunctionalInterface
public interface CacheLoader<K, V> {
    // 定义加载数据的方法
    V loader(K k);
}

  1. 使用LRU思想实现缓存类

/**
 * LRU 是数据冷热治理的一种思想  使用频率高的是热数据 使用频率低的是冷数据
 * 一般使用双向链表来使用
 */
public class LRUCache<K,V> {
    // 用于记录key值的顺序
    private final LinkedList<K> keyList = new LinkedList<>();
    // 用于存放数据
    private final Map<K,V> cache = new HashMap<>();
    // 设置数据最大容量
    private final int cacheMax;
    // cacheLoader 接口提供一种加载数据的方式 函数式接口 逻辑执行单元和runnable接口类似
    private final CacheLoader<K,V> cacheLoader;

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

    // 添加数据方法
    public void put(K key,V value){
        // 当元素超过容量时 将最老的数据清除
        if (keyList.size() >= cacheMax){
            K oldKey = keyList.removeFirst();
            cache.remove(oldKey);
        }
        // 如果数据存在先删除队列中的key 再重新放到队列尾部 不存在则直接放队列尾部
        if (keyList.contains(key)){
            keyList.remove(key);
        }
        keyList.addLast(key);
        // 数据放入
        cache.put(key,value);
    }

    // 获取数据方法
    public V get(K key){
        V value;
        // 先将key 从keyList中删除 看下是否有值
        boolean success = keyList.remove(key);
        if (!success){
            // 没有对应的key 直接通过cacheLoader接口对数据进行处理
            value = cacheLoader.loader(key);
            // 放入缓存cache
            this.put(key,value);
        }else {
            // 有对应的可以 直接从缓存中获取数据 并将key放到keyList最后面
            value = cache.get(key);
            keyList.addLast(key);
        }
        return value;
    }

    @Override
    public String toString() {
        return this.keyList.toString();
    }
}
  1. LRUCache测试类

public class LRUCacheTest {
    public static void main(String[] args) {
        LRUCache<String,Reference> cache = new LRUCache<>(5,key -> new Reference());
        cache.get("A");
        cache.get("B");
        cache.get("C");
        cache.get("D");
        cache.get("E");
        // 上面5个放进缓存 队列中第一个数据为A
        System.out.println("队列信息1:"+cache);
        // 再放一个进去A 回被挤出来
        cache.get("F");
        System.out.println("队列信息2: "+cache);
    }
}
  1. 测试强引用类型
public static void main(String[] args) throws InterruptedException {
        // 强引用
        LRUCache<Integer,Reference> cache = new LRUCache<>(200,key -> new Reference());
        // 软引用
        //SoftLRUCache<Integer,Reference> cache = new SoftLRUCache<>(200,key -> new Reference());
        for (int i=0;i<Integer.MAX_VALUE;i++){
            cache.get(i);
            TimeUnit.SECONDS.sleep(1);
            System.out.println("数据"+i+"添加到缓存中");
        }
    }

启动配置JVM参数设置内存最大和内存初始,打印GC参数
-Xmx128M -Xms64M -XX:+PrintGCDetails
在这里插入图片描述
测试结果:
在这里插入图片描述

  1. 编写软引用LRUCache类

/**
 * 和LRUCache 一样 不过其Value值被软引用 Soft Reference 封装
 * @param <K>
 * @param <V>
 */
public class SoftLRUCache<K,V> {
    private final LinkedList<K> keyList = new LinkedList<>();
    // Value值用SoftReference封装
    private final Map<K, SoftReference<V>> cache = new HashMap<>();
    private final int cacheMax;
    private final CacheLoader<K,V> cacheLoader;

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

    private void put(K key,V value){
        if (keyList.size() >= cacheMax){
            K oldKey = keyList.removeFirst();
            cache.remove(oldKey);
        }
        if (keyList.contains(key)){
            keyList.remove(key);
        }
        keyList.addLast(key);
        // 保存值也用SoftReference封装
        cache.put(key,new SoftReference<>(value));
    }

    public V get(K key){
        V value;
        boolean success = keyList.remove(key);
        if (!success){
            value = cacheLoader.loader(key);
            this.put(key,value);
        }else {
            value = cache.get(key).get();
            keyList.addLast(key);
        }
        return value;
    }

    @Override
    public String toString() {
        return this.keyList.toString();
    }
}
  1. 测试软件引用
public static void main(String[] args) throws InterruptedException {
        // 强引用
        //LRUCache<Integer,Reference> cache = new LRUCache<>(200,key -> new Reference());
        // 软引用
        SoftLRUCache<Integer,Reference> cache = new SoftLRUCache<>(200,key -> new Reference());
        for (int i=0;i<Integer.MAX_VALUE;i++){
            cache.get(i);
            TimeUnit.SECONDS.sleep(1);
            System.out.println("数据"+i+"添加到缓存中");
        }
    }

在这里插入图片描述

弱引用

// 弱应用
        Reference ref = new Reference();
        WeakReference<Reference> weakReference = new WeakReference<>(ref);
        // 当ref=null时 gc触发直接回收内存中的对象
        ref = null;
        System.gc();

虚引用


/**
 * 使用虚引用来再次触发 清理Socket对象操作
 */
public class SocketCleanTracker {
    // 定义引用队列
    private static final ReferenceQueue<Object> queue = new ReferenceQueue<>();
    static {
        new Cleaner().start();
    }

    public static void track(Socket socket){
        new Tracker(socket,queue);
    }

    private static class Cleaner extends Thread{
        private Cleaner(){
            super("清理Socket的线程");
            //设置为守护线程
            setDaemon(true);
        }

        @Override
        public void run() {
            while (true){
                try {
                    // GC回收后会将对象放入queue中
                    Tracker tracker = (Tracker) queue.remove();
                    tracker.close();
                }catch (InterruptedException e){
                }
            }
        }
    }

    // 一个虚引用
    private static class Tracker extends PhantomReference<Object>{
        private final Socket socket;
        public Tracker(Socket socket, ReferenceQueue<? super Object> q) {
            super(socket, q);
            this.socket =socket;
        }

        public void close(){
            try {
                socket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

使用虚引用再次触发Socket的关闭操作

try {
            if (socket != null){
                socket.close();
            }
        }catch (Throwable e){
            // 添加虚引用再次触发关闭关闭操作
            SocketCleanTracker.track(socket);
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值