弱引用_弱引用和软引用 - CSDN
精华内容
参与话题
  • Java基础篇 - 强引用、弱引用、软引用和虚引用

    万次阅读 多人点赞 2018-09-09 08:59:19
    引用计数:Java堆中每一个对象都有一个引用计数属性,引用每新增1次计数加1,引用每释放1次计数减1。 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象...

    前言

    Java执行GC判断对象是否存活有两种方式其中一种是引用计数

    引用计数:Java堆中每一个对象都有一个引用计数属性,引用每新增1次计数加1,引用每释放1次计数减1。

    JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于(reachable)可达状态,程序才能使用它。

    JDK 1.2版本开始,对象的引用被划分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用软引用弱引用虚引用

    正文

    (一) 强引用(StrongReference)

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

        Object strongReference = new Object();

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

        strongReference = null;

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

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

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

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

    ArrayList的Clear方法:

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

    (二) 软引用(SoftReference)

    如果一个对象只具有软引用,则内存空间充足时,垃圾回收器不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

    软引用可用来实现内存敏感的高速缓存。

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

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

        ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
        String str = new String("abc");
        SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
    
        str = null;
        // Notify GC
        System.gc();
    
        System.out.println(softReference.get()); // abc
    
        Reference<? extends String> reference = referenceQueue.poll();
        System.out.println(reference); //null

    注意:软引用对象是在jvm内存不够的时候才会被回收,我们调用System.gc()方法只是起通知作用,JVM什么时候扫描回收对象是JVM自己的状态决定的。就算扫描到软引用对象也不一定会回收它,只有内存不够的时候才会回收。

    当内存不足时,JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收:

        if(JVM内存不足) {
            // 将软引用中的对象引用置为null
            str = null;
            // 通知垃圾回收器进行回收
            System.gc();
        }

    也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软引用对象,而且虚拟机会尽可能优先回收长时间闲置不用软引用对象。对那些刚构建的或刚使用过的“较新的”软对象会被虚拟机尽可能保留,这就是引入引用队列ReferenceQueue的原因。

    应用场景:

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

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

    这时候就可以使用软引用,很好的解决了实际的问题:

        // 获取浏览器对象进行浏览
        Browser browser = new Browser();
        // 从后台程序加载浏览页面
        BrowserPage page = browser.getPage();
        // 将浏览完毕的页面置为软引用
        SoftReference softReference = new SoftReference(page);
    
        // 回退或者再次浏览此页面时
        if(softReference.get() != null) {
            // 内存充足,还没有被回收器回收,直接获取缓存
            page = softReference.get();
        } else {
            // 内存不足,软引用的对象已经回收
            page = browser.getPage();
            // 重新构建软引用
            softReference = new SoftReference(page);
        }

    (三) 弱引用(WeakReference)

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

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

    JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收:

        str = null;
        System.gc();

    注意:如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference来记住此对象。

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

        String str = new String("abc");
        WeakReference<String> weakReference = new WeakReference<>(str);
        // 弱引用转强引用
        String strongReference = weakReference.get();

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

    简单测试:

    GCTarget.java

    public class GCTarget {
        // 对象的ID
        public String id;
    
        // 占用内存空间
        byte[] buffer = new byte[1024];
    
        public GCTarget(String id) {
            this.id = id;
        }
    
        protected void finalize() throws Throwable {
            // 执行垃圾回收时打印显示对象ID
            System.out.println("Finalizing GCTarget, id is : " + id);
        }
    }

    GCTargetWeakReference.java

    public class GCTargetWeakReference extends WeakReference<GCTarget> {
        // 弱引用的ID
        public String id;
    
        public GCTargetWeakReference(GCTarget gcTarget,
                  ReferenceQueue<? super GCTarget> queue) {
            super(gcTarget, queue);
            this.id = gcTarget.id;
        }
    
        protected void finalize() {
            System.out.println("Finalizing GCTargetWeakReference " + id);
        }
    }

    WeakReferenceTest.java

    public class WeakReferenceTest {
        // 弱引用队列
        private final static ReferenceQueue<GCTarget> REFERENCE_QUEUE = new ReferenceQueue<>();
    
        public static void main(String[] args) {
            LinkedList<GCTargetWeakReference> gcTargetList = new LinkedList<>();
    
            // 创建弱引用的对象,依次加入链表中
            for (int i = 0; i < 5; i++) {
                GCTarget gcTarget = new GCTarget(String.valueOf(i));
                GCTargetWeakReference weakReference = new GCTargetWeakReference(gcTarget,
                    REFERENCE_QUEUE);
                gcTargetList.add(weakReference);
    
                System.out.println("Just created GCTargetWeakReference obj: " +
                    gcTargetList.getLast());
            }
    
            // 通知GC进行垃圾回收
            System.gc();
    
            try {
                // 休息几分钟,等待上面的垃圾回收线程运行完成
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 检查关联的引用队列是否为空
            Reference<? extends GCTarget> reference;
            while((reference = REFERENCE_QUEUE.poll()) != null) {
                if(reference instanceof GCTargetWeakReference) {
                    System.out.println("In queue, id is: " +
                        ((GCTargetWeakReference) (reference)).id);
                }
            }
        }
    }

    运行WeakReferenceTest.java,运行结果如下:

    可见WeakReference对象的生命周期基本由垃圾回收器决定,一旦垃圾回收线程发现了弱引用对象,在下一次GC过程中就会对其进行回收。

    (四) 虚引用(PhantomReference)

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

    应用场景:

    虚引用主要用来跟踪对象被垃圾回收器回收的活动。
    虚引用软引用弱引用的一个区别在于:

    虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

        String str = new String("abc");
        ReferenceQueue queue = new ReferenceQueue();
        // 创建虚引用,要求必须与一个引用队列关联
        PhantomReference pr = new PhantomReference(str, queue);

    程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要进行垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

    总结

    Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用

    垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收。

    通过表格来说明一下,如下:

    引用类型 被垃圾回收时间 用途 生存时间
    强引用 从来不会 对象的一般状态 JVM停止运行时终止
    软引用 当内存不足时 对象缓存 内存不足时终止
    弱引用 正常垃圾回收时 对象缓存 垃圾回收后终止
    虚引用 正常垃圾回收时 跟踪对象的垃圾回收 垃圾回收后终止

    欢迎关注技术公众号: 零壹技术栈

    image

    本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

    展开全文
  • 理解Java中的弱引用

    千次阅读 2019-04-15 14:07:41
    引用(Strong Reference) 强引用就是我们经常使用的引用,其写法如下 1 StringBuffer buffer = new StringBuffer(); 上面创建了一个StringBuffer对象,并将这个对象的(强)引用存到...

    强引用(Strong Reference)

    强引用就是我们经常使用的引用,其写法如下

     

    1
    
    StringBuffer buffer = new StringBuffer();
    

    上面创建了一个StringBuffer对象,并将这个对象的(强)引用存到变量buffer中。是的,就是这个小儿科的操作(请原谅我这样的说法)。强引用最重要的就是它能够让引用变得强(Strong),这就决定了它和垃圾回收器的交互。具体来说,如果一个对象通过一串强引用链接可到达(Strongly reachable),它是不会被回收的。如果你不想让你正在使用的对象被回收,这就正是你所需要的。

    但是强引用如此之强

    在一个程序里,将一个类设置成不可被扩展是有点不太常见的,当然这个完全可以通过类标记成final实现。或者也可以更加复杂一些,就是通过内部包含了未知数量具体实现的工厂方法返回一个接口(Interface)。举个例子,我们想要使用一个叫做Widget的类,但是这个类不能被继承,所以无法增加新的功能。

    但是我们如果想追踪Widget对象的额外信息,我们该怎么办? 假设我们需要记录每个对象的序列号,但是由于Widget类并不包含这个属性,而且也不能扩展导致我们也不能增加这个属性。其实一点问题也没有,HashMap完全可以解决上述的问题。

     

    1
    
    serialNumberMap.put(widget, widgetSerialNumber);
    

    这表面看上去没有问题,但是widget对象的强引用很有可能会引发问题。我们可以确信当一个widget序列号不需要时,我们应该将这个条目从map中移除。如果我们没有移除的话,可能会导致内存泄露,亦或者我们手动移除时删除了我们正在使用的widgets,会导致有效数据的丢失。其实这些问题很类似,这就是没有垃圾回收机制的语言管理内存时常遇到的问题。但是我们不用去担心这个问题,因为我们使用的时具有垃圾回收机制的Java语言。

    另一个强引用可能带来的问题就是缓存,尤其是像图片这样的大文件的缓存。假设你有一个程序需要处理用户提供的图片,通常的做法就是做图片数据缓存,因为从磁盘加载图片代价很大,并且同时我们也想避免在内存中同时存在两份一样的图片数据。

     

    缓存被设计的目的就是避免我们去再次加载哪些不需要的文件。你会很快发现在缓存中会一直包含一个到已经指向内存中图片数据的引用。使用强引用会强制图片数据留在内存,这就需要你来决定什么时候图片数据不需要并且手动从缓存中移除,进而可以让垃圾回收器回收。因此你再一次被强制做垃圾回收器该做的工作,并且人为决定是该清理到哪一个对象。

    弱引用(Weak Reference)

    弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。创建弱引用如下

     

    1
    
    WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget);
    

    使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现(当没有任何强引用到widget对象时)使用get时突然返回null。

    解决上述的widget序列数记录的问题,最简单的办法就是使用Java内置的WeakHashMap类。WeakHashMap和HashMap几乎一样,唯一的区别就是它的键(不是值!!!)使用WeakReference引用。当WeakHashMap的键标记为垃圾的时候,这个键对应的条目就会自动被移除。这就避免了上面不需要的Widget对象手动删除的问题。使用WeakHashMap可以很便捷地转为HashMap或者Map。

    引用队列(Reference Queue)

    一旦弱引用对象开始返回null,该弱引用指向的对象就被标记成了垃圾。而这个弱引用对象(非其指向的对象)就没有什么用了。通常这时候需要进行一些清理工作。比如WeakHashMap会在这时候移除没用的条目来避免保存无限制增长的没有意义的弱引用。

    引用队列可以很容易地实现跟踪不需要的引用。当你在构造WeakReference时传入一个ReferenceQueue对象,当该引用指向的对象被标记为垃圾的时候,这个引用对象会自动地加入到引用队列里面。接下来,你就可以在固定的周期,处理传入的引用队列,比如做一些清理工作来处理这些没有用的引用对象。

    四种引用

    Java中实际上有四种强度不同的引用,从强到弱它们分别是,强引用,软引用,弱引用和虚引用。上面部分介绍了强引用和弱引用,下面介绍剩下的两个,软引用和虚引用。

    软引用(Soft Reference)

    软引用基本上和弱引用差不多,只是相比弱引用,它阻止垃圾回收期回收其指向的对象的能力强一些。如果一个对象是弱引用可到达,那么这个对象会被垃圾回收器接下来的回收周期销毁。但是如果是软引用可以到达,那么这个对象会停留在内存更时间上长一些。当内存不足时垃圾回收器才会回收这些软引用可到达的对象。

    由于软引用可到达的对象比弱引用可达到的对象滞留内存时间会长一些,我们可以利用这个特性来做缓存。这样的话,你就可以节省了很多事情,垃圾回收器会关心当前哪种可到达类型以及内存的消耗程度来进行处理。

    虚引用 (Phantom Reference)

    与软引用,弱引用不同,虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。

    当弱引用的指向对象变得弱引用可到达,该弱引用就会加入到引用队列。这一操作发生在对象析构或者垃圾回收真正发生之前。理论上,这个即将被回收的对象是可以在一个不符合规范的析构方法里面重新复活。但是这个弱引用会销毁。虚引用只有在其指向的对象从内存中移除掉之后才会加入到引用队列中。其get方法一直返回null就是为了阻止其指向的几乎被销毁的对象重新复活。

     

    虚引用使用场景主要由两个。它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后在继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。

    第二点,虚引用可以避免很多析构时的问题。finalize方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了finalize方法的对象如果想要被回收掉,需要经历两个单独的垃圾收集周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。但是因为在析构过程中仍有微弱的可能这个对象会重新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象的析构之前,需要经历数量不确定的垃圾收集周期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是会出现烦人的内存溢出错误。

     

    使用虚引用,上述情况将引刃而解,当一个虚引用加入到引用队列时,你绝对没有办法得到一个销毁了的对象。因为这时候,对象已经从内存中销毁了。因为虚引用不能被用作让其指向的对象重生,所以其对象会在垃圾回收的第一个周期就将被清理掉。

    显而易见,finalize方法不建议被重写。因为虚引用明显地安全高效,去掉finalize方法可以虚拟机变得明显简单。当然你也可以去重写这个方法来实现更多。这完全看个人选择。

    总结

    我想看到这里,很多人开始发牢骚了,为什么你要讲一个过去十年的老古董API呢,好吧,以我的经验看,很多的Java程序员并不是很了解这个知识,我认为有一些深入的理解是很必要的,同时我希望大家能从本文中收获一些东西。

    原文信息

    展开全文
  • Java弱引用最精彩的解释

    千次阅读 2019-03-26 16:17:06
    原文出自:...这个解释是关于弱引用最精彩的 public class EmployeeVal { public EmployeeVal(String ...

    原文出自:https://stackoverflow.com/questions/299659/whats-the-difference-between-softreference-and-weakreference-in-java

    这个解释是关于弱引用最精彩的 

     

    public class EmployeeVal {
      public EmployeeVal(String userName) {
        this.userName = userName;
      }
    
    
      public String getUserName() {
        return userName;
      }
    
      public void setUserName(String userName) {
        this.userName = userName;
      }
    
    
      private String userName;
    
    }
        public static void main(String args[]) {
            HashMap<Employee, EmployeeVal> aMap = new HashMap<Employee, EmployeeVal>();
            Employee emp = new Employee("Vinoth");
            EmployeeVal val = new EmployeeVal("Programmer");
            aMap.put(emp, val);
            emp = null;
            System.gc();
            System.out.println("Size of Map: " + aMap.size());
        }

    输出:Size of Map: 1

      public static void main(String args[]) {
        WeakHashMap<Employee, EmployeeVal> aMap = new WeakHashMap<Employee, EmployeeVal>();
    
        Employee emp = new Employee("Vinoth");
        EmployeeVal val = new EmployeeVal("Programmer");
        aMap.put(emp, val);
        emp = null;
    
        System.gc();
        int count = 0;
    
        while (0 != aMap.size()) {
          ++count;
          System.gc();
        }
        System.out.println("Took " + count+ " calls to System.gc() to result in weakHashMap size of : "+ aMap.size());
      }

    随机每次不确定的一个count值:

    Took 3 calls to System.gc() to result in weakHashMap size of : 0

    Took 22 calls to System.gc() to result in weakHashMap size of : 0

    Took 0 calls to System.gc() to result in weakHashMap size of : 0

     

    ----------------

    Understanding Weak References Blog

    强引用

    强引用是一种普通的Java引用,是您每天使用的类型。例如,代码:

    StringBuffer buffer = new StringBuffer();

    创建一个新的StringBuffer()并在变量缓冲区中存储对它的强引用。强引用的重要部分 - 使它们变得“强大”的部分 - 是它们与垃圾收集器交互的方式。具体来说,如果一个对象可以通过一系列强引用(强烈可访问)访问,则它不符合垃圾回收的条件。由于您不希望垃圾收集器销毁您正在处理的对象,因此这通常正是您想要的。

    当强引用过于强烈时

    应用程序使用无法合理扩展的类并不罕见。该类可能只是标记为最终的,或者它可能更复杂,例如由未知(甚至可能是不可知)的具体实现支持的工厂方法返回的接口。假设您必须使用类Widget,并且无论出于何种原因,extendWidget都不可能或不实际添加新功能。

    当您需要跟踪有关对象的额外信息时会发生什么?在这种情况下,假设我们发现自己需要跟踪每个Widget的序列号,但是Widget类实际上没有序列号属性 - 并且由于Widget不可扩展,我们无法添加一个。没问题,这就是HashMaps所代表的:

    serialNumberMap.put(widget,widgetSerialNumber);

    我们必须知道(100%确定)何时不再需要特定的Widget序列号,因此我们可以从Map中删除其条目。否则我们将会发生内存泄漏(如果我们不应该删除Widgets)或者我们会莫名其妙地发现自己缺少序列号(如果我们删除了我们仍在使用的Widgets)。如果这些问题听起来很熟悉,那么它们应该是:它们正是非垃圾收集语言的用户在尝试管理内存时所面临的问题,我们不应该用像Java这样的文明语言来担心这个问题。

    强引用的另一个常见问题是缓存,特别是对于像图像这样的非常大的结构。假设您有一个必须使用用户提供的图像的应用程序,例如我工作的网站设计工具。当然,您希望缓存这些图像,因为从磁盘加载它们非常昂贵,并且您希望避免在内存中同时存储两个(可能是巨大的)图像副本的可能性。

    因为当我们不绝对需要时,图像缓存应该阻止我们重新加载图像,你会很快意识到缓存应该总是包含对已经在内存中的任何图像的引用。但是,对于普通的强引用,该引用本身将强制图像保留在内存中,这需要您(如上所述)以某种方式确定何时在内存中不再需要该图像并将其从缓存中删除,以便它变为有资格进行垃圾收集。您再次被迫复制垃圾收集器的行为并手动确定对象是否应该在内存中。

    弱引用

    简单地说,弱引用是一个不足以强迫对象保留在内存中的引用。弱引用允许您利用垃圾收集器为您确定可达性的能力,因此您不必自己执行此操作。你创建一个像这样的弱引用:

    WeakReference <Widget> weakWidget = new WeakReference <Widget>(widget);

    然后在代码的其他地方你可以使用wewWidget.get()来获取实际的Widgetobject。当然,弱引用不足以阻止垃圾收集,因此您可能会发现(如果没有对小部件的强引用)weakWidget.get()突然开始返回null。

    要解决上面的“小部件序列号”问题,最简单的方法是使用内置的WeakHashMap类.

    WeakHashMap的工作原理与HashMap完全相同,只是使用弱引用引用键(而不是值!)。如果WeakHashMap键变为垃圾,则会自动删除其条目。这避免了我描述的陷阱,并且除了从HashMap切换到WeakHashMap之外不需要任何更改。如果您遵循通过Map接口引用地图的标准惯例,则其他代码甚至不需要知道更改。

    Reference queues

    一旦WeakReference开始返回null,它指向的对象就变成了垃圾,WeakReference对象几乎没用。这通常意味着需要进行某种清理;例如,WeakHashMap必须删除这些已经过时的条目,以避免持有越来越多的deadWeakReferences。

    ReferenceQueue类可以轻松跟踪死引用。如果将ReferenceQueue传递给弱引用的构造函数,则当引用它的对象变为垃圾时,引用对象将自动插入引用队列。然后,您可以按照一定的时间间隔处理ReferenceQueue并执行死引用所需的任何清理。

    不同程度的弱引用

    到目前为止,我刚刚提到“弱引用”,但实际上有四种不同程度的参考强度:强,弱,弱和幻像,从最强到最弱。我们已经讨论过强弱参考,所以让我们来看看另外两个。

    软引用

    软引用与弱引用完全相同,只是它不太愿意丢弃它引用的对象。一个只能弱到达的对象(对它的最强引用是WeakReferences)将在下一个垃圾收集周期被丢弃,但是一个可以轻松到达的对象通常会暂停一段时间。

    SoftReferences不需要与WeakReferences有任何不同的行为,但实际上,只要内存供应充足,就可以保留软可访问对象。这使得它们成为缓存的良好基础,例如上面描述的图像缓存,因为您可以让垃圾收集器担心对象的可达性(永远不会从缓存中移除强可达对象)以及它有多糟糕需要他们消耗的记忆。

     

    幻影引用

    幻像引用与软引用或WeakReference完全不同。它对它的对象的抓握是如此脆弱,你甚至无法检索对象 - 它的get()方法总是返回null。这种引用的唯一用途是跟踪它何时被引入ReferenceQueue,因为在那时你知道它指向的对象已经死了。但是,与WeakReference有何不同?

    区别在于排队发生的时间。一旦他们指向的对象变得微弱,WeakReferences就会排队。这实际上发生在最终化或垃圾收集之前;理论上,对象甚至可以通过非正统的finalize()方法“复活”,但WeakReference仍然会死亡。只有在从内存中物理移除对象时才会将幻像引用排队,并且get()方法始终返回null,以防止您“复活”几乎死亡的对象。

    幻像引用有什么用?我只知道它们有两个严重的情况:首先,它们允许您确定何时从内存中删除对象。事实上,它们是确定这一点的唯一方法。这通常不是很有用,但在某些非常特殊的情况下可能会派上用场,例如操作大图像:如果你确定图像应该被垃圾收集,你可以等到它实际上是在尝试加载下一个图像之前,因此不太可能使可怕的OutOfMemoryError。

    其次,幻像引用避免了最终化的基本问题:f​​inalize()方法可以通过创建对它们的新的强引用来“复活”对象。那么,你说什么?好吧,问题是现在必须确定一个覆盖最终化()的对象至少在两个单独的垃圾收集周期中是垃圾才能被收集。当第一个周期确定它是垃圾时,它有资格完成。由于在完成期间对象被“复活”的(微小的,但不幸的是真实的)可能性,垃圾收集器必须在实际移除对象之前再次运行。并且由于最终确定可能没有及时发生,因此在对象等待最终确定时可能会发生任意数量的垃圾收集周期。这可能意味着实际清理垃圾对象的严重延迟,这就是为什么即使大多数堆都是垃圾也可以getOutOfMemoryErrors。

    使用PhantomReference,这种情况是不可能的 - 当PhantomReference入队时,绝对没有办法获得指向现在死对象的指针(这很好,因为它不再存在于内存中)。因为PhntomReference不能用于复活对象,所以可以在第一个垃圾收集周期中立即清理该对象,在该周期中可以发现该对象是幻象可达的。然后,您可以在方便时处置所需的任何资源。

    可以说,应该首先提供finalize()方法。 PhantomReferences肯定更安全,使用效率更高,而且甚至最大化()会使虚拟机的某些部分变得更加简单。但是,它们也需要更多的工作来实现,所以我承认在大多数时候仍然使用finalize()。好消息是,至少你有一个选择。

    ---------------

    ReferenceQueue的使用

    我们希望当一个对象被gc掉的时候通知用户线程,进行额外的处理时,就需要使用引用队列了。ReferenceQueue即这样的一个对象,当一个obj被gc掉之后,其相应的包装类,即ref对象会被放入queue中。我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。

     

    在这次处理中,map并没有因为不断加入的1M对象由产生OOM异常,并且最终运行结果之后map中的确有1万个对象。表示确实被放入了相应的对象信息。不过其中的key(即weakReference)对象中的byte[]对象却被回收了。即不断new出来的1M数组被gc掉了。

    从命令行中,我们看到有9995个对象被gc,即意味着在map的key中,除了weakReference之外,没有我们想要的业务对象。那么在这样的情况下,是否意味着这9995个entry,我们认为就是没有任何意义的对象,那么是否可以将其移除掉呢。同时还期望size值可以打印出5,而不是10000.
    WeakHashMap就是这样的一个类似实现。

     这个也可以理解为就是一个类似cache的实现。
    在cache中,key不重要并且通常都很少,value才是需要对待的。这里通过监控value变化,反向修改map,以达到控制kv的目的,避免出现无用的kv映射。

    -----------------

    利用虚引用PhantomReference实现对象被回收时收到一个系统通知

    虚引用PhantomReference, 在<<深入理解Java虚拟机>>一文中,它唯一的目的就是为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

    import java.lang.ref.PhantomReference;
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.lang.reflect.Field;
     
    public class Test {
        public static boolean isRun = true;
        @SuppressWarnings("static-access")
        public static void main(String[] args) throws Exception {
            String abc = new String("abc");
            System.out.println(abc.getClass() + "@" + abc.hashCode());
            final ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();
            new Thread() {
                public void run() {
                    while (isRun) {
                        Object obj = referenceQueue.poll();
                        if (obj != null) {
                            try {
                                Field rereferent = Reference.class
                                        .getDeclaredField("referent");
                                rereferent.setAccessible(true);
                                Object result = rereferent.get(obj);
                                System.out.println("gc will collect:"
                                        + result.getClass() + "@"
                                        + result.hashCode() + "\t"
                                        + (String) result);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }.start();
            PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc, referenceQueue);
            abc = null;
            Thread.currentThread().sleep(3000);
            System.gc();
            Thread.currentThread().sleep(3000);
            isRun = false;
        }
    }

     

    展开全文
  • 强引用,软引用,弱引用

    千次阅读 2018-05-25 14:10:39
    一、软引用和弱引用的用法软引用(SoftReference)的含义是,如果一个对象只具有软引用,而当前虚拟机堆内存空间足够,那么垃圾回收器就不会回收它,反之就会回收这些软引用指向的对象。弱引用(WeakReference)与软...
    一、软引用和弱引用的用法

    软引用(SoftReference)的含义是,如果一个对象只具有软引用,而当前虚拟机堆内存空间足够,那么垃圾回收器就不会回收它,反之就会回收这些软引用指向的对象。

    弱引用(WeakReference)与软引用的区别在于,垃圾回收器一旦发现某块内存上只有弱引用(一定请注意只有弱引用,没强引用),不管当前内存空间是否足够,那么都会回收这块内存。



    在第7行里,我们定义了SoftReference<String>类型的软引用softRef,用来指向第6行通过new创建的空间,在第13行,我 们是通过弱引用weakRef指向第12行创建的空间。

    接下来我们通过下表来观察下具体针对内存空间的操作:


    二、软引用的使用场景

    比如在一个博客管理系统里,为了提升访问性能,在用户在点击博文时,如果这篇博文没有缓存到内存中,则需要做缓存动作,这样其它用户在点击同样这篇文章时,就能直接从内存里装载,而不用走数据库,这样能降低响应时间。

    我们可以通过数据库级别的缓存在做到这点,这里也可以通过软引用来实现,具体的实现步骤如下:

    1、可以通过定义Content类来封装博文的内容,其中可以包括文章ID、文章内容、作者、发表时间和引用图片等相关信息。

    2、可以定义一个类型为HashMap<String, SoftReference<Content>>的对象类保存缓存内容,其中键是String类型,表示文章ID,值是指向Content的软引用。

    3、当用户点击某个ID的文章时,根据ID到第二步定义的HashMap里去找,如果找到,而且所对应的SoftReference<Content>值内容不是null,则直接从这里拿数据并做展示动作,这样不用走数据库,可以提升性能。

    4、如果用户点击的某个文章的ID在HashMap里找不到,或者虽然找到,但对应的值内容是空,那么就从数据库去找,找到后显示这个文章,同时再把它插入到HashMap里,这里请注意,显示后需要撤销掉这个Content类型对象上的强引用,保证它上面只有一个软引用。

    来分析下用软引用有什么好处?

    假设我们用1个G的空间缓存了10000篇文章,这10000篇文章所占的内存空间上只有软引用。如果内存空间足够,那么我们可以通过缓存来提升性能,但万一内存空间不够,我们可以依次释放这10000篇文章所占的1G内存,释放后不会影响业务流程,最多就是降低些性能。

    对比一下,如果我们这里不用软应用,而是用强引用来缓存,由于不知道文章何时将被点击,我们还无法得知什么时候可以撤销这些文章对象上的强引用,或者即使我们引入了一套缓存淘汰流程,但这就是额外的工作了,这就没刚才使用“软引用“那样方便了。

    三、通过WeakHashMap来了解弱引用的使用场景

    WeakHashMap和HashMap很相似,可以存储键值对类型的对象,但我们可以从它的名字上看出,其中的引用是弱引用。通过下面的WeakHashMapDemo.java,我们来看下它的用法。


    通过下表,我们来详细说明关键代码的含义:


    根据上文和这里的描述,我们知道如果当一个对象上只有弱引用时,这个对象会在下次垃圾回收时被回收,下面我们给出一个弱引用的使用场景。

    比如在某个电商网站项目里,我们会用Coupan这个类来保存优惠券信息,在其中我们可以定义优惠券的打折程度,有效日期和所作用的商品范围等信息。当我们从数据库里得到所有的优惠券信息后,会用一个List<Coupan>类型的coupanList对象来存储所有优惠券。

    而且,我们想要用一种数据结构来保存一个优惠券对象以及它所关联的所有用户,这时我们可以用WeakHashMap<Coupan, List<WeakReference<User>>>类型的weakCoupanHM对象。其中它的键是Coupan类型,值是指向List<User>用户列表的弱引用。

    大家可以想象下,如果有100个优惠券,那么它们会存储于List<Coupan>类型的coupanList,同时,WeakHashMap<Coupan, List<WeakReference<User>>>类型的weakCoupanHM对象会以键的形式存储这100个优惠券。而且,如果有1万个用户,那么我们可以用List<User>类型的userList对象来保存它们,假设coupan1这张优惠券对应着100个用户,那么我们一定会通过如下的代码存入这种键值对关系,weakCoupanHM.put(coupan1,weakUserList);,其中weakUserList里以弱引用的方式保存coupan1所对应的100个用户。

    这样的话,一旦当优惠券或用户发生变更,它们的对应关系就能自动地更新,具体表现如下:

    1、当某个优惠券(假设对应于coupan2对象)失效时,我们可以从coupanList里去除该对象,coupan2上就没有强引用了,只有weakCoupanHM对该对象还有个弱引用,这样coupan2对象能在下次垃圾回收时被回收,从而weakCoupanHM里就看不到了。

    2、假设某个优惠券coupan3用弱引用的方式指向于100个用户,当某个用户(假设user1)注销账号时,它会被从List<User>类型的userList对象中被移除。这时该对象上只有weakCoupanHM里的值(也就是List<WeakReference<User>>)这个弱引用,该对象同样能在下次垃圾回收时被回收,这样coupan3的关联用户就会自动地更新为99个。

    如果不用弱引用,而是用常规的HashMap<Coupan,List<User>>来保存对应关系的话,那么一旦出现优惠券或用户的变更的话,那么我们就不得不手动地更新这个表示对应关系的HashMap对象了,这样,代码就会变得复杂,而且我们很有可能因疏忽而忘记在某个位置添加更新代码。相比之下,弱引用给我们带来的“自动更新“就能给我们带来很大的便利。


    转自:

    https://mp.weixin.qq.com/s?__biz=MzI1NDQ3MjQxNA==&mid=2247486083&idx=1&sn=11fb39b27fefb7ae1c8ce1a7bc077c3c&chksm=e9c5f332deb27a24182206a4a68710ee10ee1b7b053e262c416d52ff963b4193ddca0ef9a6f4&scene=21#wechat_redirect


    展开全文
  • 弱引用的实现

    2020-01-19 21:05:25
    所谓弱引用是指所持引用不能防止其引用的对象被释放的一种指针。实现弱引用需要额外设计一个指针包装者: struct Box { void *object=nullptr; size_t refCount=0,weakCount=0; } 这个Box和object对象一样是...
  •  在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这 就像在日常生活中,从商店购买了某样物品后,如果有用,就一直保留它...
  • Java弱引用详解

    千次阅读 2017-06-12 23:50:31
     弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的 对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够, 都会回收掉只被弱引用关联的对象。 ...
  • 弱引用的使用

    2019-05-13 09:22:04
    弱引用的使用 在一些情况下,可能需要使用某个实例,但是又不想对其进行强引用而导致其不能被释放。 通常情况下,我们可以直接使用 NSPointerArray 、 NSHashTable 或 NSMapTable 类,创建一个持有弱引用实例的集合...
  • Java:强引用,软引用,弱引用和虚引用

    万次阅读 多人点赞 2019-01-28 14:24:28
    三、弱引用 四、虚引用 五、总结 在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这就像在商店购买了某样物品后,如果...
  • python 弱引用

    千次阅读 2019-04-21 17:59:48
    文章目录创建弱引用创建代理对象循环引用缓存对象 (WeakValueDictionary) 垃圾回收 和许多其它的高级语言一样,Python使用了垃圾回收器来自动销毁那些不再使用的对象。每个对象都有一个引用计数,当这个引用计数为...
  • 前言从Jdk1.2开始,在java.lang.ref包下就提供了三个类:SoftReference(软引用),PhantomReference(虚引用)和WeakReference(弱引用),它们分别代表了系统对对象的中的三种引用方式:软引用,虚引用以及弱引用...
  • 前言:满纸荒唐言,一把辛酸泪;... > 虚引用。而GC垃圾回收器(Garbage Collection)对不同的类型有着不同的处理方法,了解这些处理方式有助于我们写出更高质量的代码。 在Java中,一切被视为对象,引用则是用...
  • 1 Java引用介绍 Java从1.2版本开始引入了4种引用,这4种引用的级别由高到低依次为: 强引用 >... 弱引用 > 虚引用 ⑴强引用(StrongReference) 强引用是使用最普遍的引用。如果一个对象具...
  • ThreadLocalMap里弱引用

    千次阅读 热门讨论 2018-04-19 09:55:30
    要回答ThreadLocalMap里弱引用,我们需要弄清者三个问题第一个问题,我们先来看看引用相关的。其实Java中一直有争论关于值传递与引用传递(就我看到的百度是这样的)。我们先来看看代码。public static void main...
  • 背景:收到公众投稿,《从面试题中看Java的Reference(引用)》,分析的很不错,总感觉少了实际的例子和应用场景。于是结合自己工作中场景,小总结一下。看下Agenda如下: ...4、什么时候用软引用及弱引用
  • 软引用、弱引用和虚引用处理

    千次阅读 2017-12-27 00:18:19
    从而认识了弱引用、软引用、虚引用。今天发现Kotlin 在Android 上Anko库里的async, uiThread 里面居然做了在异步执行过程中Activity销毁了uiThread则不会调用,防止内存泄漏。正是采用了弱引用,先温习一下。Java中...
  • 软引用与强引用、弱引用、虚引用的对比 软引用的应用 软引用与强引用、弱引用、虚引用的对比 强引用 弱引用 虚引用 软引用 强引用强引用也就是我们一般使用的引用,如若一个对象有强引用,那么即使内存不足的情况...
  • 一、概述: 众所周知,Java中是JVM负责内存的分配和回收,这是它的优点(使用方便,程序不用再像使用c那样操心内存),但同时也是...在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个...
  • 年前看到一个关于软引用弱引用相关的面试题,自己之前也就听说过弱引用的概念。所以最近把相关ref包下的源码看了下,也找了点资料,把这部分内容总结一下,如下是这篇博客的大纲一.引入二.几种引用的概念和使用三.几...
  • 1、强引用(StrongReference) 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下: Object o=new Object(); // 强引用 当内存空间不足,Java虚拟机宁愿抛出...
1 2 3 4 5 ... 20
收藏数 147,567
精华内容 59,026
关键字:

弱引用