DukeDuke
主页
关于我们
主页
关于我们
  • Java

    • Java基础

      • 内存与磁盘
      • 进制转换
      • 数据存储
      • Java基本数据类型
      • HashMap
      • Java四大引用
    • JVM

      • 认识JVM
      • JVM类加载器
      • 运行时数据区
      • 执行引擎
      • 本地方法接口
      • 本地方法库
      • JVM垃圾回收
      • JVM性能监控
      • JVM调优
    • 设计模式
      • 单例模式
      • 工厂模式
      • 策略模式
      • 适配器模式
      • 建造者模式
      • 原型模式
      • 装饰器模式
      • 代理模式
      • 外观模式
      • 享元模式
      • 组合模式
      • 桥接模式
    • Java多线程

      • Java 线程基础详解
      • Java 线程池详解
      • Java ThreadLocal 详解
      • Java volatile 详解
      • Java 线程间通信详解
      • Java 线程安全详解
      • Java 线程调度详解
      • Java 线程优先级详解

      • Java 线程中断详解
      • Java 线程死锁详解
    • Java反射
    • Java 面试题

      • Java 基础概念面试题
      • Java 面向对象编程面试题
      • Java 集合框架面试题
      • Java 多线程与并发面试题
      • JVM 与内存管理面试题
      • Java I/O 与 NIO 面试题
      • Java 异常处理面试题
      • Java 反射与注解面试题
      • Java Spring 框架面试题
      • Java 数据库与 JDBC 面试题
      • Java 性能优化面试题
      • Java 实际项目经验面试题
      • Java 高级特性面试题
      • Java 面试准备建议

深入理解Java四大引用:强引用、软引用、弱引用与虚引用

1. 引言

Java中的引用类型决定了对象何时可以被垃圾回收器回收。理解不同类型的引用对于编写高性能、内存友好的Java应用程序至关重要。本文将深入探讨Java的四大引用类型,并结合SpringBoot实际应用场景进行详细说明。

2. Java引用类型概述

Java从JDK 1.2版本开始,将引用分为四种类型,它们按照引用强度从强到弱依次为:

  1. 强引用(Strong Reference)
  2. 软引用(Soft Reference)
  3. 弱引用(Weak Reference)
  4. 虚引用(Phantom Reference)

2.1 引用类型关系图

3. 强引用(Strong Reference)

3.1 定义与特点

强引用是Java中最常见的引用类型,也是默认的引用类型。只要强引用存在,垃圾回收器就永远不会回收被引用的对象。

特点:

  • 即使内存不足,抛出OutOfMemoryError也不会回收强引用对象
  • 强引用可以直接访问目标对象
  • 当强引用被设置为null时,对象才会被标记为可回收

3.2 代码示例

// 强引用示例
String str = new String("Hello World");  // str是强引用
Object obj = new Object();               // obj是强引用

// 将引用置为null,对象变为可回收状态
str = null;
obj = null;

3.3 SpringBoot中的强引用应用

在SpringBoot中,所有的Bean默认都是强引用:

@Service
public class UserService {
    // userRepository是强引用
    @Autowired
    private UserRepository userRepository;
    
    // users列表中的元素都是强引用
    private List<User> users = new ArrayList<>();
    
    public void addUser(User user) {
        users.add(user);  // 添加强引用
    }
}

4. 软引用(Soft Reference)

4.1 定义与特点

软引用用来描述一些还有用但并非必需的对象。在系统将要发生内存溢出之前,会回收这些对象。如果回收后内存仍然不足,才会抛出OutOfMemoryError。

特点:

  • 内存充足时,软引用对象不会被回收
  • 内存不足时,软引用对象会被回收
  • 适合用于实现内存敏感的缓存

4.2 软引用生命周期图

4.3 代码示例

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

public class SoftReferenceExample {
    public static void main(String[] args) {
        // 创建软引用
        String str = new String("Hello Soft Reference");
        SoftReference<String> softRef = new SoftReference<>(str);
        
        // 清除强引用
        str = null;
        
        // 获取软引用对象
        String retrieved = softRef.get();
        if (retrieved != null) {
            System.out.println("对象还存在: " + retrieved);
        } else {
            System.out.println("对象已被回收");
        }
    }
}

4.4 SpringBoot中的软引用应用:缓存实现

在SpringBoot中,可以使用软引用实现内存敏感的缓存:

package com.example.demo.cache;

import org.springframework.stereotype.Component;
import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 基于软引用的缓存实现
 * 当内存不足时,缓存会自动释放,避免OOM
 */
@Component
public class SoftReferenceCache<K, V> {
    
    private final ConcurrentHashMap<K, SoftReference<V>> cache = new ConcurrentHashMap<>();
    
    /**
     * 放入缓存
     */
    public void put(K key, V value) {
        cache.put(key, new SoftReference<>(value));
    }
    
    /**
     * 获取缓存
     */
    public V get(K key) {
        SoftReference<V> softRef = cache.get(key);
        if (softRef == null) {
            return null;
        }
        
        V value = softRef.get();
        // 如果对象已被回收,移除该条目
        if (value == null) {
            cache.remove(key);
        }
        return value;
    }
    
    /**
     * 清除已被回收的引用
     */
    public void cleanUp() {
        cache.entrySet().removeIf(entry -> entry.getValue().get() == null);
    }
    
    /**
     * 获取缓存大小
     */
    public int size() {
        cleanUp();
        return cache.size();
    }
}

使用示例:

package com.example.demo.service;

import com.example.demo.cache.SoftReferenceCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
    
    @Autowired
    private SoftReferenceCache<String, Product> productCache;
    
    /**
     * 获取产品信息(带缓存)
     */
    public Product getProduct(String productId) {
        // 先从缓存获取
        Product product = productCache.get(productId);
        if (product != null) {
            return product;
        }
        
        // 缓存未命中,从数据库加载
        product = loadProductFromDatabase(productId);
        
        // 放入缓存(使用软引用,内存不足时自动释放)
        productCache.put(productId, product);
        
        return product;
    }
    
    private Product loadProductFromDatabase(String productId) {
        // 模拟数据库查询
        return new Product(productId, "Product Name", 99.99);
    }
}

5. 弱引用(Weak Reference)

5.1 定义与特点

弱引用比软引用更弱,无论内存是否充足,只要发生垃圾回收,弱引用对象就会被回收。

特点:

  • 下一次GC时就会被回收
  • 适合用于实现规范映射(canonicalizing mappings)
  • 常用于WeakHashMap

5.2 弱引用生命周期图

5.3 代码示例

import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

public class WeakReferenceExample {
    public static void main(String[] args) {
        // 创建弱引用
        String str = new String("Hello Weak Reference");
        WeakReference<String> weakRef = new WeakReference<>(str);
        
        // 清除强引用
        str = null;
        
        // 强制GC(仅用于演示,生产环境不推荐)
        System.gc();
        
        // 检查对象是否被回收
        String retrieved = weakRef.get();
        if (retrieved != null) {
            System.out.println("对象还存在: " + retrieved);
        } else {
            System.out.println("对象已被GC回收");
        }
    }
}

5.4 SpringBoot中的弱引用应用:监听器管理

在SpringBoot中,可以使用弱引用管理事件监听器,避免内存泄漏:

package com.example.demo.listener;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;

/**
 * 基于弱引用的事件监听器管理器
 * 当监听器对象不再被使用时,自动清理引用
 */
@Component
public class WeakReferenceListenerManager {
    
    private final List<WeakReference<ApplicationListener<?>>> listeners = new ArrayList<>();
    
    /**
     * 注册监听器(使用弱引用)
     */
    public void registerListener(ApplicationListener<?> listener) {
        listeners.add(new WeakReference<>(listener));
        // 清理已被回收的引用
        cleanUp();
    }
    
    /**
     * 获取所有活跃的监听器
     */
    public List<ApplicationListener<?>> getActiveListeners() {
        cleanUp();
        List<ApplicationListener<?>> activeListeners = new ArrayList<>();
        for (WeakReference<ApplicationListener<?>> ref : listeners) {
            ApplicationListener<?> listener = ref.get();
            if (listener != null) {
                activeListeners.add(listener);
            }
        }
        return activeListeners;
    }
    
    /**
     * 清理已被回收的引用
     */
    private void cleanUp() {
        listeners.removeIf(ref -> ref.get() == null);
    }
}

WeakHashMap应用示例:

package com.example.demo.cache;

import org.springframework.stereotype.Component;
import java.util.WeakHashMap;
import java.util.Map;

/**
 * 基于WeakHashMap的元数据缓存
 * 当key对象不再被使用时,自动清理对应的缓存条目
 */
@Component
public class MetadataCache {
    
    // WeakHashMap:当key对象被回收时,对应的entry也会被自动移除
    private final Map<Object, String> metadataCache = new WeakHashMap<>();
    
    /**
     * 存储元数据
     */
    public void putMetadata(Object key, String metadata) {
        metadataCache.put(key, metadata);
    }
    
    /**
     * 获取元数据
     */
    public String getMetadata(Object key) {
        return metadataCache.get(key);
    }
    
    /**
     * 获取缓存大小
     */
    public int size() {
        return metadataCache.size();
    }
}

6. 虚引用(Phantom Reference)

6.1 定义与特点

虚引用是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取对象实例。

特点:

  • 无法通过get()方法获取对象(总是返回null)
  • 必须与ReferenceQueue配合使用
  • 主要用于跟踪对象被垃圾回收的状态
  • 适合用于资源清理

6.2 虚引用工作流程图

6.3 代码示例

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceExample {
    public static void main(String[] args) {
        // 创建引用队列
        ReferenceQueue<String> queue = new ReferenceQueue<>();
        
        // 创建对象和虚引用
        String str = new String("Hello Phantom Reference");
        PhantomReference<String> phantomRef = new PhantomReference<>(str, queue);
        
        // 清除强引用
        str = null;
        
        // 虚引用无法直接获取对象
        System.out.println("通过虚引用获取对象: " + phantomRef.get()); // 输出: null
        
        // 强制GC
        System.gc();
        
        // 检查引用队列
        try {
            PhantomReference<?> ref = (PhantomReference<?>) queue.remove(1000);
            if (ref != null) {
                System.out.println("对象已被回收,可以进行资源清理");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6.4 SpringBoot中的虚引用应用:资源清理

在SpringBoot中,可以使用虚引用实现资源的自动清理:

package com.example.demo.resource;

import org.springframework.stereotype.Component;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 基于虚引用的资源清理管理器
 * 当对象被GC回收时,自动清理相关资源
 */
@Component
public class ResourceCleanupManager {
    
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
    private final ConcurrentHashMap<PhantomReference<Object>, Runnable> cleanupTasks = new ConcurrentHashMap<>();
    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    
    public ResourceCleanupManager() {
        // 启动清理线程
        executor.scheduleWithFixedDelay(this::processQueue, 1, 1, TimeUnit.SECONDS);
    }
    
    /**
     * 注册需要清理的资源
     * @param object 被监控的对象
     * @param cleanupTask 清理任务
     */
    public void registerCleanup(Object object, Runnable cleanupTask) {
        PhantomReference<Object> ref = new PhantomReference<>(object, queue);
        cleanupTasks.put(ref, cleanupTask);
    }
    
    /**
     * 处理引用队列,执行清理任务
     */
    private void processQueue() {
        PhantomReference<Object> ref;
        while ((ref = (PhantomReference<Object>) queue.poll()) != null) {
            Runnable cleanupTask = cleanupTasks.remove(ref);
            if (cleanupTask != null) {
                try {
                    cleanupTask.run();
                    System.out.println("资源清理完成");
                } catch (Exception e) {
                    System.err.println("资源清理失败: " + e.getMessage());
                }
            }
        }
    }
    
    /**
     * 关闭管理器
     */
    public void shutdown() {
        executor.shutdown();
    }
}

使用示例:文件资源管理

package com.example.demo.service;

import com.example.demo.resource.ResourceCleanupManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

@Service
public class FileService {
    
    @Autowired
    private ResourceCleanupManager cleanupManager;
    
    /**
     * 打开文件,当文件对象被GC回收时自动关闭流
     */
    public FileInputStream openFile(String filePath) throws IOException {
        File file = new File(filePath);
        FileInputStream fis = new FileInputStream(file);
        
        // 注册清理任务:当file对象被回收时,关闭文件流
        cleanupManager.registerCleanup(file, () -> {
            try {
                if (fis != null) {
                    fis.close();
                    System.out.println("文件流已自动关闭: " + filePath);
                }
            } catch (IOException e) {
                System.err.println("关闭文件流失败: " + e.getMessage());
            }
        });
        
        return fis;
    }
}

7. ReferenceQueue(引用队列)

7.1 引用队列的作用

ReferenceQueue用于配合软引用、弱引用和虚引用使用。当引用的对象被垃圾回收后,Java会将这个引用加入到与之关联的ReferenceQueue中。

使用场景:

  • 跟踪对象被回收的状态
  • 执行清理操作
  • 实现更精确的资源管理

7.2 引用队列工作流程

7.3 完整示例:结合ReferenceQueue的资源管理

package com.example.demo.resource;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 结合ReferenceQueue的缓存实现
 */
public class ReferenceQueueCache<K, V> {
    
    private final ConcurrentHashMap<K, WeakReference<V>> cache = new ConcurrentHashMap<>();
    private final ReferenceQueue<V> queue = new ReferenceQueue<>();
    
    /**
     * 放入缓存
     */
    public void put(K key, V value) {
        // 清理已被回收的引用
        cleanUp();
        
        // 创建弱引用并关联队列
        WeakReference<V> ref = new WeakReference<>(value, queue);
        cache.put(key, ref);
    }
    
    /**
     * 获取缓存
     */
    public V get(K key) {
        cleanUp();
        WeakReference<V> ref = cache.get(key);
        return ref != null ? ref.get() : null;
    }
    
    /**
     * 清理已被回收的引用
     */
    private void cleanUp() {
        WeakReference<? extends V> ref;
        while ((ref = (WeakReference<? extends V>) queue.poll()) != null) {
            // 从缓存中移除已被回收的条目
            cache.entrySet().removeIf(entry -> entry.getValue() == ref);
            System.out.println("清理了已被回收的缓存条目");
        }
    }
}

8. 四大引用对比总结

8.1 对比表格

引用类型回收时机get()方法使用场景示例
强引用永不回收(除非置null)返回对象正常对象引用String str = new String()
软引用内存不足时回收返回对象或null内存敏感缓存图片缓存、数据缓存
弱引用GC时回收返回对象或null临时对象、监听器WeakHashMap、事件监听
虚引用GC时回收总是返回null资源清理、跟踪文件流关闭、资源释放

8.2 引用强度关系图

9. SpringBoot实战:综合应用案例

9.1 场景:实现一个智能缓存系统

结合四种引用类型,实现一个智能的缓存系统:

package com.example.demo.cache;

import org.springframework.stereotype.Component;
import java.lang.ref.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 智能缓存系统
 * - 热点数据使用强引用(快速访问)
 * - 普通数据使用软引用(内存敏感)
 * - 临时数据使用弱引用(自动清理)
 */
@Component
public class SmartCache<K, V> {
    
    // 热点数据:使用强引用
    private final ConcurrentHashMap<K, V> hotCache = new ConcurrentHashMap<>();
    private static final int HOT_CACHE_SIZE = 100;
    
    // 普通数据:使用软引用
    private final ConcurrentHashMap<K, SoftReference<V>> softCache = new ConcurrentHashMap<>();
    
    // 临时数据:使用弱引用
    private final ConcurrentHashMap<K, WeakReference<V>> weakCache = new ConcurrentHashMap<>();
    private final ReferenceQueue<V> queue = new ReferenceQueue<>();
    
    /**
     * 放入缓存(根据访问频率自动选择引用类型)
     */
    public void put(K key, V value, CacheLevel level) {
        switch (level) {
            case HOT:
                // 热点数据:强引用
                if (hotCache.size() >= HOT_CACHE_SIZE) {
                    // 移除最旧的数据,降级为软引用
                    Map.Entry<K, V> oldest = hotCache.entrySet().iterator().next();
                    hotCache.remove(oldest.getKey());
                    softCache.put(oldest.getKey(), new SoftReference<>(oldest.getValue()));
                }
                hotCache.put(key, value);
                break;
            case NORMAL:
                // 普通数据:软引用
                softCache.put(key, new SoftReference<>(value));
                break;
            case TEMPORARY:
                // 临时数据:弱引用
                weakCache.put(key, new WeakReference<>(value, queue));
                cleanUp();
                break;
        }
    }
    
    /**
     * 获取缓存
     */
    public V get(K key) {
        // 1. 先从热点缓存获取
        V value = hotCache.get(key);
        if (value != null) {
            return value;
        }
        
        // 2. 从软引用缓存获取
        SoftReference<V> softRef = softCache.get(key);
        if (softRef != null) {
            value = softRef.get();
            if (value != null) {
                // 提升为热点数据
                promoteToHot(key, value);
                return value;
            } else {
                softCache.remove(key);
            }
        }
        
        // 3. 从弱引用缓存获取
        WeakReference<V> weakRef = weakCache.get(key);
        if (weakRef != null) {
            value = weakRef.get();
            if (value != null) {
                return value;
            } else {
                weakCache.remove(key);
            }
        }
        
        return null;
    }
    
    /**
     * 提升为热点数据
     */
    private void promoteToHot(K key, V value) {
        if (hotCache.size() < HOT_CACHE_SIZE) {
            hotCache.put(key, value);
            softCache.remove(key);
        }
    }
    
    /**
     * 清理已被回收的弱引用
     */
    private void cleanUp() {
        WeakReference<? extends V> ref;
        while ((ref = (WeakReference<? extends V>) queue.poll()) != null) {
            weakCache.entrySet().removeIf(entry -> entry.getValue() == ref);
        }
    }
    
    /**
     * 缓存级别枚举
     */
    public enum CacheLevel {
        HOT,        // 热点数据
        NORMAL,     // 普通数据
        TEMPORARY   // 临时数据
    }
}

9.2 使用示例

package com.example.demo.service;

import com.example.demo.cache.SmartCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DataService {
    
    @Autowired
    private SmartCache<String, Object> smartCache;
    
    /**
     * 获取数据(带智能缓存)
     */
    public Object getData(String key) {
        // 先从缓存获取
        Object data = smartCache.get(key);
        if (data != null) {
            return data;
        }
        
        // 缓存未命中,从数据源加载
        data = loadFromDataSource(key);
        
        // 根据数据特性选择缓存级别
        SmartCache.CacheLevel level = determineCacheLevel(key, data);
        smartCache.put(key, data, level);
        
        return data;
    }
    
    /**
     * 根据数据特性确定缓存级别
     */
    private SmartCache.CacheLevel determineCacheLevel(String key, Object data) {
        // 业务逻辑:根据key或data的特性决定缓存级别
        if (key.startsWith("hot_")) {
            return SmartCache.CacheLevel.HOT;
        } else if (key.startsWith("temp_")) {
            return SmartCache.CacheLevel.TEMPORARY;
        } else {
            return SmartCache.CacheLevel.NORMAL;
        }
    }
    
    private Object loadFromDataSource(String key) {
        // 模拟数据加载
        return "Data for " + key;
    }
}

10. 最佳实践与注意事项

10.1 使用建议

  1. 强引用:默认使用,适用于所有正常场景
  2. 软引用:适用于实现缓存,特别是内存敏感的缓存
  3. 弱引用:适用于临时对象、监听器、元数据等
  4. 虚引用:适用于资源清理、对象回收跟踪

10.2 注意事项

// ❌ 错误:不要这样使用软引用
SoftReference<String> ref = new SoftReference<>(new String("test"));
String str = ref.get();  // 可能立即返回null(如果对象被回收)

// ✅ 正确:先创建强引用,再创建软引用
String str = new String("test");
SoftReference<String> ref = new SoftReference<>(str);
// 使用完str后,再清除强引用
str = null;

10.3 常见陷阱

  1. 过早回收:弱引用和虚引用可能在任何时候被回收,需要做好null检查
  2. 内存泄漏:忘记清理ReferenceQueue可能导致内存泄漏
  3. 性能影响:频繁的引用检查可能影响性能

11. 总结

Java的四大引用类型提供了灵活的内存管理机制:

  • 强引用:最常用,保证对象不会被意外回收
  • 软引用:实现内存敏感的缓存,平衡性能和内存使用
  • 弱引用:自动清理临时对象,避免内存泄漏
  • 虚引用:跟踪对象回收,实现资源清理

在SpringBoot应用中,合理使用这些引用类型可以帮助我们:

  • 优化内存使用
  • 实现高效的缓存系统
  • 避免内存泄漏
  • 实现资源的自动清理

理解并正确使用Java的引用机制,是编写高质量、高性能Java应用程序的重要技能。

最近更新:: 2025/12/29 11:07
Contributors: Duke
Prev
HashMap