关于四大引用你还记得吗?
强引用、弱引用、软引用、虚引用
那么你还记得他们的作用吗?分别的应用场景?
那么今天主要来探讨一下软引用,首先,软引用是什么?
软引用是四大引用之一,主要是用来标记内存中敏感的缓存的对象,如果内存充足时不回收,内存不足时才回收。
首先区分一下内存和缓存,他们有什么区别?
注意!它们不是同一层次的概念,内存是存储空间,缓存是加速机制。
像内存、磁盘他们属性存储空间或介质,但是缓存是一种机制,他可以在内存、磁盘、CPU等等(是可选的)缓存的本质是副本,丢了也没关系!
搞清楚之后就来探讨一下软引用为啥适合标记内存中敏感的缓存对象中?
因为软引用的回收时机本来就不确定,只有GC 在内存不足时回收,如果内存充足时是不会回收软引用的对象,所以这种不确定性就决定了他的适用场景,而且刚刚说了,缓存的本质是副本,缓存的数据可以重新加载,所以适合标记内存中敏感的缓存对象
那么软引用的真实应用场景有哪些?
数据库连接缓存
public class ConnectionCache {
private Map<String, SoftReference<Connection>> cache = new HashMap<>();
public Connection get(String url) {
SoftReference<Connection> ref = cache.get(url);
if (ref != null) {
Connection conn = ref.get();
if (conn != null && !conn.isClosed()) return conn;
cache.remove(url);
}
Connection conn = DriverManager.getConnection(url);
cache.put(url, new SoftReference<>(conn));
return conn;
}
}
比如这段代码,他把连接对象放入软引用对象中,内存充足时就可以从缓存中直接获取连接对象,只有内存不足时才会把这个软引用的连接对象进行回收,这个从一定程度上也提高了获取连接对象的性能
总之软引用的适用场景就是在缓存中,那么使用软引用的过程中需要注意什么呢?
回收时机不可控
因为他的会输时机不可控,如果在内存中,那么一定要考虑内存的空间问题,如果内存空间充足,那么软引用的对象在缓存中的命中率就会很高,如果内存空间小,那么就会触发频繁的GC问题!!!那么缓存机会全部失效,而且频繁GC还会增肌性能开销
讲完了软引用的使用场景和注意事项,那么还有一个问题需要探讨的是什么呢?
那就是软引用对象的回收机制!
Object referent = new Object();
SoftReference<Object> ref = new SoftReference<>(referent, queue);
看清楚两个对象分别是什么!!!
| 对象 | 说明 |
|---|---|
referent | 被引用的对象 |
ref | 软引用对象本身 |
现在要研究的是ref软引用对象本身是怎么回收的?
一开始我认为就是直接通过if判断ref的被引用对象是否为空不就行了?!
如果某个软引用的对象的被引用对象为空,那么说明这个软引用对象可以被回收
问题在于,如果软引用对象有100 万个呢?不可能一个个去遍历吧?!!
所以我们确定不了软引用对象数量,那么这里底层设计了一个队列来实现软引用对象的回收
直接把软引用对象放入队列中,然后就一个个取出来处理,如果用我的判断加遍历的方式那么复杂度是遍历所有项,O(n),但是队列的话只处理失效项,O(1)。
Reference<?> ref;
while ((ref = queue.poll()) != null) {
// ...
}
还有问题是时机问题:不知道什么时候进行校验,每次访问缓存时检查?还是定期后台线程检查?
还是每次 GC 后检查?这个是不确定的,如果采用我的if判断的方式进行检查那么就会出现很多问题
比如第一种:每次访问缓存时检查,那么没有访问的软引用对象就永远不会被检查,最后导致内存泄漏!!!
第二种:定期后台线程检查,首先这种方案需要额外的线程开销,其次他的轮询周期难以确定,还有就是并不能带来及时性,时效性差!!!
第三种:每次 GC 后检查但是JVM 没有提供“GC 后回调”机制,所以无解
还有线程安全问题,如果采用if判断的方式,那么在并发情况下必须加锁,那么加锁又会有性能开销。
总结之后发现:通过 ReferenceQueue 实现软引用对象的回收机制太巧妙了!!!
也许这就是队列的好处吧!!!在线程安全、时机、性能等等方面都考虑的很好
| 维度 | 队列方式 | if 判断方式 |
|---|---|---|
| 复杂度 | 均摊 O(1) | O(n) |
| 时机 | GC 后立即 | 需要主动轮询 |
| 并发 | 线程安全 | 需要加锁 |
| 内存泄漏 | 队列精确清理 | 引用对象可能泄漏 |
| GC 集成 | 利用 GC 遍历 | 重复遍历 |
| 实时性 | 高 | 低 |
| 精确性 | 精确知道哪个引用失效 | 可能误判 |
| 额外线程 | 不需要 | 需要或定期检查 |
| 适用场景 | 缓存、清理 | 简单场景 |
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2401_83418369/article/details/166141452




