😁前言
引用类型是java中很重要的一个知识,也是很容易忽略的一个知识点。经常背八股文的同学肯定已经很熟悉,但是你们知道在什么场景用吗?在实际开发中用过吗?今天主要分享一下弱引用的使用场景。
如果还不熟悉引用类型的同学,就先看看基础知识吧!
引用类型基础知识
- 1. 强引用(Strong Reference): 强引用是最常见的引用类型,通过
new
关键字创建的对象都属于强引用。只要强引用存在,垃圾回收器(GC)就不会回收被引用的对象。
java
代码解读
复制代码
Object obj = new Object(); // 强引用 obj = null; // 置为null后,对象可被GC回收
- 2. 软引用(SoftReference): 软引用通过
SoftReference
类实现。当内存充足时,软引用的对象不会被回收;但当内存不足时,GC 会回收这些对象。(缓存场景)
java
代码解读
复制代码
SoftReference<String> softRef = new SoftReference<>(new String("Hello")); // 获取对象 String str = softRef.get(); System.out.println(str); // 输出: Hello // 手动触发GC(内存充足时软引用对象可能不会被回收) System.gc(); str = softRef.get(); System.out.println(str); // 可能输出: Hello(取决于内存情况)
- 3. 弱引用(WeakReference): 弱引用通过
WeakReference
类实现。无论内存是否充足,只要 GC 扫描到弱引用对象,就会立即回收它。(临时关联对象,避免内存泄漏)
java
代码解读
复制代码
// 创建弱引用 WeakReference<String> weakRef = new WeakReference<>(new String("Weak")); // 获取对象 String str = weakRef.get(); System.out.println(str); // 输出: Weak // 手动触发GC(弱引用对象会被立即回收) System.gc(); str = weakRef.get(); System.out.println(str); // 输出: null
- 4. 虚引用(PhantomReference):虚引用通过
PhantomReference
类实现。它无法直接获取对象,主要用于在对象被回收时收到系统通知,通常配合引用队列使用。(实际的业务系统开发使用场景也非常少,本篇文章就不过多的介绍了)
java
代码解读
复制代码
// 创建引用队列 ReferenceQueue<String> queue = new ReferenceQueue<>(); // 创建虚引用(必须关联引用队列) PhantomReference<String> phantomRef = new PhantomReference<>( new String("Phantom"), queue ); // 虚引用无法获取对象 System.out.println(phantomRef.get()); // 输出: null // 手动触发GC System.gc(); Thread.sleep(100); // 等待GC完成 // 检查引用队列 if (queue.poll() != null) { System.out.println("对象已被回收"); // 输出: 对象已被回收 }
📖在springboot 中的运用场景
springboot中软弱引用的的地方用得非常多,关键是现实类是 org.springframework.util.ConcurrentReferenceHashMap
工具类, 这是 Spring 框架自带的一个工具类,支持soft、weak 两种应用类型,默认soft引用。 springboot 中 使用软弱应用也主要是通过调用该工具类来实现的缓存。
ConcurrentReferenceHashMap 核心代码
java
代码解读
复制代码
/** * The reference type: SOFT or WEAK. */ private final ReferenceType referenceType; .................. public ConcurrentReferenceHashMap( int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) { Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative"); Assert.isTrue(loadFactor > 0f, "Load factor must be positive"); Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive"); Assert.notNull(referenceType, "Reference type must not be null"); this.loadFactor = loadFactor; this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL); int size = 1 << this.shift; this.referenceType = referenceType; int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size); int initialSize = 1 << calculateShift(roundedUpSegmentCapacity, MAXIMUM_SEGMENT_SIZE); //------------------------------------ Segment[] segments = (Segment[]) Array.newInstance(Segment.class, size); int resizeThreshold = (int) (initialSize * getLoadFactor()); for (int i = 0; i < segments.length; i++) { segments[i] = new Segment(initialSize, resizeThreshold); } this.segments = segments; }
2.Segment 构造器
java
代码解读
复制代码
public Segment(int initialSize, int resizeThreshold) { this.referenceManager = createReferenceManager(); this.initialSize = initialSize; this.references = createReferenceArray(initialSize); this.resizeThreshold = resizeThreshold; }
3.createReference
arduino
代码解读
复制代码
public Reference<K, V> createReference(Entry<K, V> entry, int hash, @Nullable Reference<K, V> next) { if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { return new WeakEntryReference<>(entry, hash, next, this.queue); } return new SoftEntryReference<>(entry, hash, next, this.queue); }
springbooot 如何使用ConcurrentReferenceHashMap
springboot 中使用ConcurrentReferenceHashMap的地方非常的多
今天简单看看其中几个类吧,常见的BeanUtils.java
、SpringFactoriesLoader
、SpringConfigurationPropertySources
、TreadPoolTaskExcutor
1. BeanUtils 中运用
根据约定规则(Convention over Configuration),为给定的目标类型(targetType
)查找对应的 PropertyEditor
编辑器。 将查找失败的类型记录到 unknownEditorTypes
集合中,避免后续重复尝试加载,提升性能。使用弱引用类型缓存数据,在内存不足发生GC时targetType
不存在强引用时将会被回收。
java
代码解读
复制代码
private static final Set<Class<?>> unknownEditorTypes = Collections.newSetFromMap(new ConcurrentReferenceHashMap<>(64)); .......................................... @Nullable public static PropertyEditor findEditorByConvention(@Nullable Class<?> targetType) { if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) { return null; } ClassLoader cl = targetType.getClassLoader(); if (cl == null) { try { cl = ClassLoader.getSystemClassLoader(); if (cl == null) { return null; } } catch (Throwable ex) { // e.g. AccessControlException on Google App Engine return null; } } String targetTypeName = targetType.getName(); String editorName = targetTypeName + "Editor"; try { Class<?> editorClass = cl.loadClass(editorName); if (editorClass != null) { if (!PropertyEditor.class.isAssignableFrom(editorClass)) { unknownEditorTypes.add(targetType); return null; } return (PropertyEditor) instantiateClass(editorClass); } // Misbehaving ClassLoader returned null instead of ClassNotFoundException // - fall back to unknown editor type registration below } catch (ClassNotFoundException ex) { // Ignore - fall back to unknown editor type registration below } unknownEditorTypes.add(targetType); return null; }
2.SpringFactoriesLoader 中运用
Spring 框架中用于加载 META-INF/spring.factories
文件的核心工具,实现了 Spring 的自动发现机制(Auto-detection),ConcurrentReferenceHashMap
充当缓存器,类加载器作为Key, 类加载器加载配置文件的结果做value。
java
代码解读
复制代码
static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>(); ...................... private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
3.SpringConfigurationPropertySources 中的运用
将 Spring 标准的 PropertySource
对象转换为 Spring Boot 特有的 ConfigurationPropertySource
,使用ConcurrentReferenceHashMap
缓存避免重复适配相同的 PropertySource
,提升配置读取效率。在内存不足发生GC回收没有被强引用的对象,来减少OOM
的风险
java
代码解读
复制代码
private final Map<PropertySource<?>, ConfigurationPropertySource> cache = new ConcurrentReferenceHashMap<>(16, ReferenceType.SOFT); .............................. private ConfigurationPropertySource adapt(PropertySource<?> source) { ConfigurationPropertySource result = this.cache.get(source); // Most PropertySources test equality only using the source name, so we need to // check the actual source hasn't also changed. if (result != null && result.getUnderlyingSource() == source) { return result; } result = SpringConfigurationPropertySource.from(source); if (source instanceof OriginLookup) { result = result.withPrefix(((OriginLookup<?>) source).getPrefix()); } this.cache.put(source, result); return result; }
4. TreadPoolTaskExcutor.java
看看线程池的初始化,和 线程池关闭在shutdown
时候调用关闭剩余任务的cancelRemainingTask
方法
java
代码解读
复制代码
// Runnable decorator to user-level FutureTask, if different private final Map<Runnable, Object> decoratedTaskMap = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); ..................... //1. 初始化线程池 protected ExecutorService initializeExecutor( ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { BlockingQueue<Runnable> queue = createQueue(this.queueCapacity); ThreadPoolExecutor executor; if (this.taskDecorator != null) { executor = new ThreadPoolExecutor( this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler) { @Override public void execute(Runnable command) { Runnable decorated = taskDecorator.decorate(command); if (decorated != command) { decoratedTaskMap.put(decorated, command); } super.execute(decorated); } }; } else { executor = new ThreadPoolExecutor( this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler); } if (this.allowCoreThreadTimeOut) { executor.allowCoreThreadTimeOut(true); } if (this.prestartAllCoreThreads) { executor.prestartAllCoreThreads(); } this.threadPoolExecutor = executor; return executor; } ............................. //在线程池关闭时取消剩余任务,在线程池关闭时取消剩余任务.当装饰任务(键)或原始任务(值)不再有其他强引用时,会被垃圾回收器自动清理,避免内存占用。 /** Perform a shutdown on the underlying ExecutorService. * See Also: * ExecutorService.shutdown(), ExecutorService.shutdownNow() **/ @Override protected void cancelRemainingTask(Runnable task) { super.cancelRemainingTask(task); // Cancel associated user-level Future handle as well Object original = this.decoratedTaskMap.get(task); if (original instanceof Future) { ((Future<?>) original).cancel(true); } }
看了几个springboot
中使用软引用的场景我想大家对软弱因应用的使用场景已经比较清楚了吧。还不清楚的就看看下面几个简单的小例子吧!
🏹Java 内存管理技巧 实战
在构建可扩展的 Java 应用程序时,高效的内存使用和性能变得至关重要。当然呢,实际开发中很多服务的缓存都存放到了redis中,所以大家对 软弱应用的使用或者了解就非常的少了。
对于开发中没有使用redis
的应用来说,软弱应用来实现缓存那就非常的重要了。下面就给大家展示几个软弱引用的例子吧
ConcurrentHashMap
缓存数据库查询结果、计算结果等大对象,在内存不足的情况下发生GC自动释放。
因为现在很多缓存数据,都是存放在redis中了,所以用软应用的就不多了。如果没有用redis的情况,使用软引用时非常不错的
java
代码解读
复制代码
//缓存数据:比如缓存的一些 组织机构呀等信息 private final ConcurrentHashMap<String, SoftReference<Object>> cache = new ConcurrentHashMap<>(); public Object getData(String key) { SoftReference<Object> ref = cache.get(key); Object data = ref != null ? ref.get() : null; if (data == null) { data = loadDataFromDatabase(key); cache.put(key, new SoftReference<>(data)); } return data; } // 模拟数据库查询 private Object loadDataFromDatabase(String key) { return new byte[1024 * 1024]; // 1MB数据 }
WeakHashMap 和 WeakHashSet:智能内存友好型容器
WeakHashMap 实战
java
代码解读
复制代码
import java.util.Map; import java.util.WeakHashMap; public class WeakMapExample { public static void main(String[] args) { Map<Object, String> map = new WeakHashMap<>(); Object key = new Object(); map.put(key, "Some value"); System.out.println("Before GC: " + map.size()); // 输出1 key = null; // 移除强引用 System.gc(); // 提示GC进行回收 try { Thread.sleep(100); } catch (InterruptedException ignored) {} System.out.println("After GC: " + map.size()); // 可能输出0 } }
💡 原理:当key的强引用被移除后,GC 会自动清理。key会被垃圾回收,因为map
仅持有对它的弱引用。
创建 WeakHashSet
java
代码解读
复制代码
import java.util.*; public class WeakSetExample { public static void main(String[] args) { Set<Object> weakSet = Collections.newSetFromMap(new WeakHashMap<>()); Object item = new Object(); weakSet.add(item); System.out.println("Before GC: " + weakSet.size()); // 输出1 item = null; System.gc(); try { Thread.sleep(100); } catch (InterruptedException ignored) {} System.out.println("After GC: " + weakSet.size()); // 可能输出0 } }
应用场景:用于GC时对象可以被回收的缓存(如临时数据、监听器注册等)。
⚙️ 预定义集合大小以提升性能
当您知道(或可以估算)元素数量时,设置初始容量可以节省时间和内存。
🆚 默认大小与预定义大小的 ArrayList 对比
java
代码解读
复制代码
List<String> dynamicList = new ArrayList<>(); // 默认初始容量10 List<String> preSizedList = new ArrayList<>(1000); // 预定义容量1000 for (int i = 0; i < 1000; i++) { dynamicList.add("Item " + i); preSizedList.add("Item " + i); }
预定义大小的优势:
preSizedList
在增长过程中避免了多次内部数组扩容,从而:
- 减少堆内存波动
- 降低 GC 循环频率
- 内存使用更可预测
预定义 HashMap 大小
java
代码解读
复制代码
Map<Integer, String> map = new HashMap<>(1000); // 初始容量1000 for (int i = 0; i < 1000; i++) { map.put(i, "Value " + i); }
内部机制:HashMap 的每次扩容都需要重新哈希所有条目,成本较高。预定义大小可减少此类操作。
基准测试(推荐)
如果需要确认具体场景下的性能提升,可使用 JMH(Java 微基准测试工具) 测试内存使用和执行时间差异。