zoukankan      html  css  js  c++  java
  • Java基础|强引用、弱引用、软引用、虚引用

    前言

    在ThreadLocal源码中,其中嵌套类ThreadLocalMap中的Entry继承了WeakReferenc。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。

    强引用(StrongReference)

    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:

     //强引用 
    Object o=new Object(); 

    当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:

    // 帮助垃圾收集器回收此对象 
    o=null;

    显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。

    举例:

    public void test(){ 
        Object o=new Object(); 
        // 省略其他操作 
    } 

    在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。

    但是如果这个o是全局变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。

    强引用在实际中又非常重要的用处,举个ArrayList的实现源代码:

    private transient Object[] elementData; 
    public void clear() { 
        modCount++; 
        // Let gc do its work 
        for (int i = 0; i < size; i++) 
           elementData[i] = null; 
        size = 0; 
    } 

    在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。

    软引用(SoftReference)

    软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

    如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

    // 强引用
    String str=new String("abc"); 
    // 软引用 
    SoftReference<String> softRef=new SoftReference<String>(str);    

    当内存不足时,等价于:

    if(JVM.内存不足()) { 
       // 转换为软引用 
       str = null;  
       // 垃圾回收器进行回收 
       System.gc(); 
    } 

    软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

    • 如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
    • 如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

    这时候就可以使用软引用

    // 获取页面进行浏览 
    Browser prev = new Browser();
    // 浏览完毕后置为软引用
    SoftReference sr = new SoftReference(prev); 
    if(sr.get()!=null){  
        // 还没有被回收器回收,直接获取
        rev = (Browser) sr.get();
    }else{ 
        // 由于内存吃紧,所以对软引用的对象回收了
        prev = new Browser();
        // 重新构建
        sr = new SoftReference(prev);
    } 

    这样就很好的解决了实际的问题。

    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

    弱引用(WeakReference)

    弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。

    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

    String str=new String("abc");     
    WeakReference<String> abcWeakRef = new WeakReference<String>(str); 
    str=null; 

    当垃圾回收器进行扫描回收时等价于:

    str = null; 
    System.gc(); 

    如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 WeakReference 来记住此对象。

    下面的代码会让str再次变为一个强引用:

    String  abc = abcWeakRef.get(); 

    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。

    这个引用不会在对象的垃圾回收判断中产生任何附加的影响

    import java.lang.ref.Reference; 
    import java.lang.ref.ReferenceQueue; 
    import java.lang.ref.WeakReference; 
    import java.util.LinkedList; 
     
    public class ReferenceTest { 
     
        private static ReferenceQueue<NiuhBig> rq = new ReferenceQueue<NiuhBig>(); 
     
        public static void checkQueue() { 
            Reference<? extends NiuhBig> ref = null; 
            while ((ref = rq.poll()) != null) { 
                if (ref != null) { 
                    System.out.println("In queue: "    + ((NiuhBigWeakReference) (ref)).id); 
                } 
            } 
        } 
     
        public static void main(String args[]) { 
            int size = 3; 
            LinkedList<WeakReference<NiuhBig>> weakList = new LinkedList<WeakReference<NiuhBig>>(); 
            for (int i = 0; i < size; i++) { 
                weakList.add(new NiuhBigWeakReference(new NiuhBig("Weak " + i), rq)); 
                System.out.println("Just created weak: " + weakList.getLast()); 
     
            } 
     
            System.gc(); 
            try { // 下面休息几分钟,让上面的垃圾回收线程运行完成 
                Thread.currentThread().sleep(6000); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
            checkQueue(); 
        } 
    } 
     
    class NiuhBig { 
        public String id; 
        // 占用空间,让线程进行回收 
        byte[] b = new byte[2 * 1024]; 
     
        public NiuhBig(String id) { 
            this.id = id; 
        } 
     
        protected void finalize() { 
            System.out.println("Finalizing NiuhBig " + id); 
        } 
    } 
     
    class NiuhBigWeakReference extends WeakReference<NiuhBig> { 
        public String id; 
     
        public NiuhBigWeakReference(NiuhBig big, ReferenceQueue<NiuhBig> rq) { 
            super(big, rq); 
            this.id = big.id; 
        } 
     
        protected void finalize() { 
            System.out.println("Finalizing NiuhBigWeakReference " + id); 
        } 
    } 

    最后的输出结果为:

    Just created weak: com.niuh.NiuhBigWeakReference@3d075dc0 
    Just created weak: com.niuh.NiuhBigWeakReference@214c265e 
    Just created weak: com.niuh.NiuhBigWeakReference@448139f0 
    Finalizing NiuhBig Weak 2 
    Finalizing NiuhBig Weak 1 
    Finalizing NiuhBig Weak 0 
    In queue: Weak 2 
    In queue: Weak 1 
    In queue: Weak 0 

    虚引用(PhantomReference)

    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。 **

    总结

    Java 4种引用的级别由高到低依次为:

    • 强引用 > 软引用 > 弱引用 > 虚引用

    通过图来看一下他们之间在垃圾回收时的区别:

                   

    当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收,通过表格来说明一下,如下:

                   

    在实际程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

  • 相关阅读:
    POJ 1330 Nearest Common Ancestors(LCA Tarjan算法)
    LCA 最近公共祖先 (模板)
    线段树,最大值查询位置
    带权并查集
    转负二进制
    UVA 11437 Triangle Fun
    UVA 11488 Hyper Prefix Sets (字典树)
    UVALive 3295 Counting Triangles
    POJ 2752 Seek the Name, Seek the Fame (KMP)
    UVA 11584 Partitioning by Palindromes (字符串区间dp)
  • 原文地址:https://www.cnblogs.com/rinack/p/14077838.html
Copyright © 2011-2022 走看看