乐趣区

关于java:Difference-between-SoftReference-and-WeakReference-in-Java

【Java】What’s the difference between SoftReference and WeakReference in Java

原文

What’s the difference between SoftReference and WeakReference in Java?

From Understanding Weak References, by Ethan Nicholas:

引言

在学习 JVM 的过程中大概率会看到相似 SoftReferenceWeakReference的字样,本局部筛选了 Stack Flow 上的高赞答复进行整顿。

Understanding Weak References

Understanding Weak References

尽管是一篇 2006 年的博客,然而写的十分好,并且至今也仍然有不小的价值,这里简略翻译和提取一下这篇文章的内容。

后面一大段阐明写这篇文章的原因是面试的时候发现很多面试者甚至不晓得这个货色,相比之下本人不仅聊了作用和一些细节反差很大。

Now, I’m not suggesting you need to be a weak reference expert to qualify as a decent Java engineer. But I humbly submit that you should at least _know what they are_ — otherwise how will you know when you should be using them? Since they seem to be a little-known feature, here is a brief overview of what weak references are, how to use them, and when to use them.

当初,我并不是说你须要成为一个单薄的参考资料专家,才有资格成为一个体面的 Java 工程师。但我虚心地认为,你至多应该晓得它们是什么 — 否则你怎么会晓得什么时候应该应用它们?因为它们仿佛是一个鲜为人知的个性,这里简要介绍一下什么是弱援用,如何应用它们,以及何时应用它们。

Strong references

首先理解一下强援用,任何的 new 操作都是一个强援用:

StringBuffer buffer = new StringBuffer();

If an object is reachable via a chain of strong references (strongly reachable), it is not eligible for garbage collection.

如果一个对象具备强援用,并且援用链可达到,那么垃圾回收器就没有资格进行回收。

When strong references are too strong

这部分大段文字描述的含意表明,有时候设计零碎会把一些强援用对象放到 HashMap 这样的汇合容器当中,它长的像这样:

serialNumberMap.put(widget, widgetSerialNumber);

外表上看这仿佛工作的很好,然而咱们必须晓得(有 100% 的把握)何时不再须要这个对象,一旦把这个对象溢出,很有可能存在暗藏的内存透露或者强援用无奈开释导致无奈垃圾回收的问题。

强援用的另外一个问题是缓存,假如你有一个须要解决用户提供的图片的应用程序,你想要利用缓存来缩小图像加载带来的磁盘开销,同时要防止两份截然不同的图像同时存在内存当。

对于一般的强援用来说,这个援用自身就会迫使图像留在内存中,这就要求你(就像下面一样)以某种形式确定何时不再须要内存中的图像,并将其从缓存中删除,这样它就有资格被垃圾回收。

最要命的是这种状况下甚至须要思考垃圾回收器的工作行为。

Weak references

弱援用:简略来说是一个不够弱小的援用,不足以迫使一个对象留在内存中。弱援用能够让程序员依据垃圾回收器的行为大抵估算援用的生命周期。换句话说,弱援用必定会在下一次垃圾收集器给回收掉,程序员无需放心相似强援用传递的问题。

在 Java 当中,通常用 java.lang.ref.WeakReference 类来示意。

public class Main {public static void main(String[] args) {WeakReference<String> sr = new WeakReference<String>(new String("hello"));
         
        System.out.println(sr.get());
        System.gc();                // 告诉 JVM 的 gc 进行垃圾回收
        System.out.println(sr.get());
    }
}/** 输入后果:hello
null
**/

留神 被弱援用关联的对象是指只有弱援用与之关联,如果存在强援用同时与之关联,则进行垃圾回收时也不会回收该对象(软援用也是如此)

ReferenceQueue 利用

此外,弱援用能够和一个援用队列(ReferenceQueue)联结应用,如果弱援用所援用的对象被 JVM 回收,这个援用就会被退出到与之关联的援用队列中。

 @Test  
    public void referenceQueueUse() throws InterruptedException {Object value = new Object();  
        Map<Object, Object> map = new HashMap<>();  
        ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();  
        for(int i = 0;i < 10000;i++) {byte[] bytes = new byte[1024 * 1024];  
            WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, referenceQueue);  
            map.put(weakReference, value);  
        }  
        // 应用主线程察看 referenceQueue 的对象回收比拟麻烦  
        Thread thread = new Thread(() -> {  
            try {  
                int cnt = 0;  
                WeakReference<byte[]> k;  
                // 期待垃圾回收  
                while((k = (WeakReference) referenceQueue.remove()) != null) {System.out.println((cnt++) + "被回收了:" + k);  
                }  
            } catch(InterruptedException e) {// 完结循环}  
        });  
//        thread.setDaemon(true);  
        thread.start();  
        thread.join();}/**  
     0 被回收了:java.lang.ref.WeakReference@138caeca  
     1 被回收了:java.lang.ref.WeakReference@3402b4c9  
     2 被回收了:java.lang.ref.WeakReference@75769ab0  
     3 被回收了:java.lang.ref.WeakReference@2e23c180  
     4 被回收了:java.lang.ref.WeakReference@4aaae508  
     5 被回收了:java.lang.ref.WeakReference@3e84111a  
     6 被回收了:java.lang.ref.WeakReference@2cc04358  
     7 被回收了:java.lang.ref.WeakReference@45bb2aa1  
     8 被回收了:java.lang.ref.WeakReference@1639f93a  
     9 被回收了:java.lang.ref.WeakReference@f3021cb  
     */

下面的代码执行之后会触发垃圾回收,中的对象援用变为 null 就意味着对象是小并且将会被垃圾回收,WeakHashMap 必须删除这种生效的条目,以防止持有一直减少的死的 WeakReference。

在 WeakReference 中传入 ReferenceQueue,在援用生效的时候会被推入到 ReferenceQueue 当中。

在类 weakHashMap 中的应用

实际上 weakHashMap 就是应用 weakReference 当作 key 来进行数据的存储,所以 key 被 gc 之后响应的 gc 也能够移除掉。

WeakHashMap Weak 在于 Key 而非 Value,WeakHashMap 的 Key 没有其余强援用时,这些 key 就可能被回收,且 WeakHashMap 可能会回收这些 Key 对应的键值对。

这里要举比拟容易出错的例子,那就是 String 自身为对象,然而如果是 String xx = "xxx"这样的定义形式,因为 key 会被加载常量池当中主动取得强援用,实际上不会被垃圾回收。

@Test  
    public void weakHashMap(){WeakHashMap<String, String> map = new WeakHashMap<>();  
        // 上面的正文阐明 value 对于 WeakHashMap 过期没有任何影响,不论它是否在常量池当中有强援用。String value = new String("value");  
//        String value = "value";  
  
        // new String 此时编译器无奈感知,编译阶段无奈提前感知,没有强援用保留。//        map.put(new String("key"), value); // {key=value}、// 对于可在编译阶段确定的字符串,零碎的字符串常量池会间接记录它,主动保留对它的强援用  
        map.put("key", value); // {key=value}  
        System.out.println(map);  
  
        System.gc();  
        System.runFinalization();  
  
        System.out.println(map);  
    }/** 运行后果:如果应用 new String("key") 则为上面的后果  
     {key=value}  
     {}  
     因为间接应用了字符串字面量“key”,造成了系统对“key”字符串的缓存,对其施加了强援用,因而 GC 未能销毁此实例  
     {key=value}  
     {key=value}     */

再看看上面的办法,能够看到如果让 key,value 的援用同时应用一个援用,value 理论应用的还是强援用,因为 Key 和 Value 都存在强援用,所以这种状况下也会呈现 key 无奈回收的问题:

/**  
 * 模仿 Intern 办法  
 * @param <T>  
 */  
class SimulaIntern<T> {private WeakHashMap<T, T> weakHashMap = new WeakHashMap<T, T>();  
  
    /**  
     * 修复之后的对象应用  
     */  
    private WeakHashMap<String, MyObject> weakHashMapFix = new WeakHashMap<>();  
  
    /**  
     * 此办法存在问题  
     * @description 此办法存在问题  
     * @param item  
     * @return T  
     * @author xander  
     * @date 2023/6/16 14:48  
     */    public T intern(T item){if(weakHashMap.get(item) != null){return weakHashMap.get(item);  
        }  
        // 本源在于这里的 put,对于 key 来说是弱援用,然而 value 仍旧是强援用保留  
        weakHashMap.put(item, item);  
        return item;  
    }  
  
    public MyObject intern(String key, MyObject value) {MyObject object = weakHashMapFix.get(key);  
        if (object != null) {return object;}  
        weakHashMapFix.put(key, value);  
        return value;  
    }  
  
    public void print(){System.out.println("weakHashMap =>"+ weakHashMap);  
        System.out.println("weakHashMapFix =>"+weakHashMapFix);  
    }  
  
}  
  
class MyObject{ }  
  
@Test  
public void testWeakMap(){  
    // intern 存在问题  
    SimulaIntern<String> testPool = new SimulaIntern<>();  
    testPool.intern(new String("testPool"));  
    testPool.print();  
    System.gc();  
    System.runFinalization();  
    testPool.print();  
  
    // 能够失常清理,实际上就是把 k 和 value 两者进行拆分  
    SimulaIntern simulaIntern = new SimulaIntern();  
    simulaIntern.intern(new String("name"), new MyObject());  
    simulaIntern.print();  
    System.gc();  
    System.runFinalization();  
    simulaIntern.print();}/** 运行后果:// 无奈失常清理  
 weakHashMap => {testPool=testPool}  
 weakHashMap => {testPool=testPool}  
 // 批改之后,能够失常清理,实际上就是把 k 和 value 两者进行拆分  
 weakHashMapFix => {name=com.zxd.interview.weakref.WeakReferenceTest$MyObject@1936f0f5}  
 weakHashMapFix => {}*/

Soft references

软援用和弱援用的区别是,弱援用在没有强援用加持的状况下,通常必然会在下一次垃圾回收给清理掉,但软援用则略微强一些,个别会保持更长的工夫,个别状况下如果内存足够,软援用根本会被保留。

所以软银用的特色是:只有内存供给短缺,Soft reachable 对象通常会被保留。如果既放心垃圾回收提前解决掉对象,又放心内存耗费的需要,就能够思考软援用。

Phantom references

虚援用和软援用和弱援用齐全不一样,它的 get 办法常常返回 Null,虚援用的惟一用处是跟踪什么时候对象会被放入到援用队列,那么这种放入援用队列的形式和弱援用有什么区别?

次要的区别是入队的工夫,WeakReference 在它所指向的对象变为弱援用(无强援用)的时候会被推入到队列,因为能够在 queue 中获取到弱援用对象,该对象甚至能够通过非正统的 finalize()办法 “ 复活 ”,但 WeakReference 依然是死的。

PhantomReference只有在 对象被从内存中物理删除时才会被排队 ,而且 get() 办法总是返回 null,次要是为了避免你可能 “ 复活 “ 一个简直死去的对象。所以 PhantomReference 会有什么用:

  • 它们容许你精确地确定一个对象何时从内存中删除。
  • PhantomReferences 防止了 finalize()的一个根本问题。

第一点精确的确定一个对象何时删除,造某些大内存占用的状况下能够实现期待垃圾回收再加载下一个图片避免 OOM。

第二点则 finalize()办法能够通过为对象创立新的强援用来 “ 复活 “ 它们,然而虚援用能够很大概率防止。当一个 PhantomReference 被排队时,相对没有方法取得当初曾经死亡的对象的指针,该对象能够在第一个垃圾收集周期中被发现能够幻象地达到时被立刻清理掉。

Stack Flow 的答案

这些答案根本都来自下面提到的博客雷同,这里不再过多赘述。

Weak references

A _weak reference_, simply put, is a reference that isn’t strong enough to force an object to remain in memory. Weak references allow you to leverage the garbage collector’s ability to determine reachability for you, so you don’t have to do it yourself. You create a weak reference like this

WeakReference weakWidget = new WeakReference(widget);

and then elsewhere in the code you can use weakWidget.get() to get the actual Widget object. Of course the weak reference isn’t strong enough to prevent garbage collection, so you may find (if there are no strong references to the widget) that weakWidget.get() suddenly starts returning null.

Soft references

A soft reference is exactly like a weak reference, except that it is less eager to throw away the object to which it refers. An object which is only weakly reachable (the strongest references to it are WeakReferences) will be discarded at the next garbage collection cycle, but an object which is softly reachable will generally stick around for a while.

SoftReferences aren’t required to behave any differently than WeakReferences, but in practice softly reachable objects are generally retained as long as memory is in plentiful supply. This makes them an excellent foundation for a cache, such as the image cache described above, since you can let the garbage collector worry about both how reachable the objects are (a strongly reachable object will never be removed from the cache) and how badly it needs the memory they are consuming.

最初的补充局部简略翻译一下

And Peter Kessler added in a comment:

上面是评论中的一些补充

The Sun JRE does treat SoftReferences differently from WeakReferences. We attempt to hold on to object referenced by a SoftReference if there isn’t pressure on the available memory.

Sun JRE 看待 SoftReferences 的形式与 WeakReferences 不同。如果可用内存并且此时没有应用压力,会尝试保留被 SoftReference 援用的对象。

One detail: the policy for the “-client” and “-server” JRE’s are different: the -client JRE tries to keep your footprint small by preferring to clear SoftReferences rather than expand the heap, whereas the -server JRE tries to keep your performance high by preferring to expand the heap (if possible) rather than clear SoftReferences. One size does not fit all.

“-client “ 和 ”-server “JRE 的策略是不同的:

  • -client:JRE 试图通过 优先革除 SoftReferences而不是扩大堆来放弃你的内存。
  • -server:JRE 则试图通过优先扩大堆(如果可能)而不是革除 SoftReferences 来放弃你的性能
退出移动版