实战SoftReference被回收的时机

本文探讨了一种基于客户端的服务注册发现框架中使用的轮询负载均衡算法,并深入研究了使用SoftReference作为缓存容器时可能遇到的问题及其原因。

这是我最近在开发的一个基于客户端发现模式(因为基于服务端发现的都比较多了,consul还做得很好)的服务注册发现框架:

  https://github.com/leoChaoGlut/ServiceDIscoveryAndRegistry


然后在过程中遇到这样一个问题:


在做client-service-proxy的时候,要实现一个负载均衡算法.


我选了比较常用的轮询分发算法.


在实现过程中,需要将发送过的相同的host和port缓存下来,以便不要再次分发到相同的节点.如果一个服务的所有节点都发送过了,则会清理缓存,重新轮询.


问题来了,实现缓存,如果直接用new HashSet()这样的方式存储,如果内部的元素一直被引用,和GC Root一直有通路,则该对象不会被GC.


但有时候该对象其实又不会经常被用到,存着还是浪费内存.


所以一般会用SoftReference<Set<T>>软引用方式来做缓存容器.


SoftReference的源码介绍里也有提到,它可以被用作memory-sensitive caches.


问题又来了,SoftReference被GC的情况和想象中不一样!!!!!!!!!!!!!


我在测试的时候,用VisualVM来监控测试程序,当我利用VisualVM手动执行Full GC时,SoftReference竟然没有被GC!!!!!!!!


于是乎我又去看了源码的解释,如下:


All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an <code>OutOfMemoryError</code>.


意思是在OOM发生之前,它才会被GC掉,而没有发生OOM之前的GC,是不会将SoftReference的对象GC掉的!


会导致的问题:

SoftReference<Set<String>> recordSetRef = new SoftReference<>(new HashSet<>());//1
Set<String> recordSet = recordSetRef.get();//2
if (recordSet.isEmpty()) {//3. 如果运气非常不好...这里可能会报空指针.因为执行到上面那一句的时候,该线程正好失去CPU时间片,且正好即将发生OOM(正常的GC不会清理SoftReference),则此时recordSet会是null
}

根据Java 内存模型:一个代码块内,如果前后的代码不存在关联关系,则可能会发生指令重排序.(当然,上例中没有出现指令重排序).


当然,一个线程对应一个方法栈,如果两个线程之间的数据没有依赖关系,也是不会发生并发线程安全问题的.


如果要避免这样的情况,可以将该代码块用synchronized包裹起来,如果频繁被调用的话,不建议加锁,应该另辟出路.


因为加了锁,就算当前线程失去时间片,在该线程没有执行完同步块之前,其它线程是无法访问其中的数据的.等到线程重新获得时间片,可以继续执行未执行完的代码.


问题在三到来:也许有人想到了,如果加了同步块,其它线程不能访问其中的内容,那要是GC线程呢?好吧,这我也不知道,等我知道了我再告诉你........


有知道的朋友希望可以解答~谢谢~
 

### SoftReference 介绍 `SoftReference` 是 Java 中用于实现软引用的类,它位于 `java.lang.ref` 包下。软引用是一种比强引用弱一些的引用类型。当一个对象仅被软引用所引用时,在系统内存充足的情况下,该对象不会被垃圾回收;但当系统内存不足时,垃圾回收器会回收这些仅被软引用引用的对象,以释放内存。这一特性使得软引用非常适合用于实现缓存机制,因为缓存中的数据通常不是必需的,在内存紧张时可以被回收,从而避免内存溢出错误。 ### 使用方法 以下是一个简单的使用 `SoftReference` 的示例代码: ```java import java.lang.ref.SoftReference; public class SoftReferenceExample { public static void main(String[] args) { // 创建一个对象 String data = new String("Some data"); // 创建一个软引用指向该对象 SoftReference<String> softReference = new SoftReference<>(data); // 通过软引用获取对象 String retrievedData = softReference.get(); if (retrievedData != null) { System.out.println("Retrieved data: " + retrievedData); } else { System.out.println("Data has been garbage collected."); } // 断开强引用 data = null; // 尝试触发垃圾回收(不一定会立即回收) System.gc(); // 再次通过软引用获取对象 retrievedData = softReference.get(); if (retrievedData != null) { System.out.println("Retrieved data after GC: " + retrievedData); } else { System.out.println("Data has been garbage collected after GC."); } } } ``` 在上述代码中,首先创建了一个 `String` 对象 `data`,然后使用 `SoftReference` 对其进行包装。通过 `softReference.get()` 方法可以获取被引用的对象。当将 `data` 置为 `null` 后,该对象仅被软引用引用。调用 `System.gc()` 尝试触发垃圾回收,再次通过软引用获取对象时,可能会得到 `null`,表示对象已被回收。 ### 应用场景 - **缓存系统**:在缓存系统中,使用软引用可以在内存充足时保留缓存数据,提高系统性能;而在内存不足时,缓存数据会被自动回收,避免内存溢出。例如,网页浏览器的缓存机制,当用户访问过的网页数据被软引用引用时,在内存充足时可以快速从缓存中获取网页内容,而在内存紧张时,这些缓存数据会被回收。 - **图片加载**:在移动应用或桌面应用中,加载大量图片时,使用软引用可以避免因图片占用过多内存而导致的内存溢出问题。当内存不足时,图片数据会被垃圾回收,需要时再重新加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值