zoukankan      html  css  js  c++  java
  • [转载] Java的四种引用关系

    版权声明: 本文为转载文章, 转载时有适量修改. 再次转载时请附上原文出处链接和本声明.
    原文链接: https://blog.csdn.net/u013256816/article/details/50907595

    Java中提供了4个级别的引用: 强引用、软引用、弱引用和虚引用, 这四个引用定义在包java.lang.ref下:

    Java四种引用关系的源码位置

    1 强引用 (Final Reference)

    强引用就是指在程序代码中普遍存在的、类似于Object obj = new Object() 的引用, 只要强引用还存在, 垃圾收集器就不会去回收这些被引用的对象.

    强引用有以下三个特点:

    1. 强引用可以直接访问目标对象;
    2. 强引用锁指向的对象在任何时候都不会被系统回收 ——JVM宁愿抛出OOM异常也不回收强引用所指向的对象;
    3. 强引用可能导致内存泄露, 比如List中添加了new出来的对象, List失去被回收之后, 其内部的对象不能被访问、却又不会被回收的现象.

    FinalReference类的全部定义如下:

    package java.lang.ref;
    /**
     * Final references, used to implement finalization
     */
    class FinalReference<T> extends Reference<T> {
        public FinalReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    

    FinalReference只有一个构造函数: 根据给定对象的引用和引用队列构造一个强引用.

    2 软引用 (Soft Reference)

    软引用用来描述一些还有用但并非必须的对象: 被软引用关联着的对象, 如果内存充足, 则垃圾回收器不会回收该对象;
    如果内存不够用, 就会回收这些对象.

    在系统将要发生OutOfMemoryError之前, JVM会把被软引用关联着的对象列进回收范围, 并进行第二次回收. 如果这次回收之后内存仍然不够用, 系统才会抛出OutOfMemoryError.

    从JDK 1.2开始提供了SoftReference类来实现软引用: 与一个引用队列 (ReferenceQueue) 联合使用实现内存敏感的高速缓存 —— 如果软引用所引用的对象被垃圾回收器回收, JVM就会把这个软引用加入到与之关联的引用队列中.

    2.1 案例1: 软引用的垃圾回收

    package com.healchow.java.detail;
    
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.lang.ref.SoftReference;
    
    public class SoftRefTest {
        private static ReferenceQueue<MyObject> softQueue = new ReferenceQueue<>();
    
        private static class MyObject {
            @Override
            protected void finalize() throws Throwable {
                super.finalize();
                System.out.println("MyObject's finalize called");
            }
            @Override
            public String toString() {
                return "I am MyObject";
            }
        }
    
        private static class CheckRefQueue implements Runnable {
            Reference<MyObject> obj = null;
            @Override
            public void run() {
                try {
                    obj = (Reference<MyObject>) softQueue.remove();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (obj != null) {
                    System.out.println("Object for SoftReference is " + obj.get());
                }
            }
        }
        
        public static void main(String[] args) {
            MyObject obj = new MyObject();
            SoftReference<MyObject> softRef = new SoftReference<>(obj, softQueue);
            new Thread(new CheckRefQueue()).start();
    
            // 删除强引用, 否则obj不会被回收
            obj = null;
            System.gc();
            System.out.println("After GC: Soft Get = " + softRef.get());
            System.out.println("尝试分配大块内存...");
            byte[] b = new byte[5 * 1024 * 725];
            System.out.println("After new byte[]: Soft Get = " + softRef.get());
            System.gc();
        }
    }
    

    (1) 测试方法1:

    在运行测试主方法时设置VM参数: -Xmx5M, 也就是指定该程序的Java Heap最大为5MB, 运行结果为:

    After GC: Soft Get = I am MyObject
    尝试分配大块内存...
    After new byte[]: Soft Get = I am MyObject
    MyObject's finalize called
    Object for SoftReference is null
    

    案例代码解释:

    ① 首先构造MyObject对象, 并将其赋值给object变量, 构成强引用.

    ② 然后使用SoftReference构造这个MyObject对象的软引用softRef, 并注册到softQueue引用队列 —— 当softRef被回收时, 会被加入softQueue队列.

    ③ 设置obj = null, 删除这个强引用, 这时系统内对MyObject对象的引用只剩下软引用.

    ④ 显示调用GC, 通过软引用的get()方法获取MyObject对象的引用, 发现对象并未被回收, 这说明GC在内存充足的情况下, 不会回收软引用对象.

    ⑤ 接着请求一块大的堆空间5*1024*725(要多次调整使得垃圾回收工作能顺利进行、线程能顺利退出), 这个操作会使系统堆内存使用紧张, 从而产生新一轮的GC. 在这次GC后, softRef.get()不再返回MyObject对象, 而是返回null —— 说明在系统内存紧张的情况下, 软引用被回收. 软引用被回收时, 会被加入注册的引用队列, 此时引用队列中有了元素, 开辟的多线程中softQueue.remove()不再阻塞, 因此程序得以成功退出.

    如果将上面案例中的数组再改大点, 比如5*1024*1024, 就会抛出OOM异常:

    After GC: Soft Get = I am MyObject
    尝试分配大块内存...
    MyObject's finalize called
    Exception in thread "main" Object for SoftReference is null
    java.lang.OutOfMemoryError: Java heap space
    	at com.healchow.java.detail.SoftRefTest.main(SoftRefTest.java:56)
    

    (2) 测试方法2:

    在运行测试主方法时设置VM参数: -Xmx5M -XX:PrintGCDetails, 打印出GC的日志信息(关于GC日志可以查看《Java堆内存http://blog.csdn.net/u013256816/article/details/50764532》), 运行结果为:

    [GC (Allocation Failure) [PSYoungGen: 1024K->480K(1536K)] 1024K->480K(5632K), 0.0013139 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (System.gc()) [PSYoungGen: 1383K->480K(1536K)] 1383K->544K(5632K), 0.0011186 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC (System.gc()) [PSYoungGen: 480K->0K(1536K)] [ParOldGen: 64K->504K(4096K)] 544K->504K(5632K), [Metaspace: 3316K->3316K(1056768K)], 0.0044642 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
    After GC: Soft Get = I am MyObject
    尝试分配大块内存...
    [GC (Allocation Failure) [PSYoungGen: 38K->64K(1536K)] 542K->568K(5632K), 0.0009263 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 64K->32K(1536K)] 568K->536K(5632K), 0.0011345 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    [Full GC (Allocation Failure) [PSYoungGen: 32K->0K(1536K)] [ParOldGen: 504K->504K(4096K)] 536K->504K(5632K), [Metaspace: 3317K->3317K(1056768K)], 0.0038031 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 504K->504K(5632K), 0.0007999 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 504K->486K(4096K)] 504K->486K(5632K), [Metaspace: 3317K->3317K(1056768K)], 0.0037241 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
    MyObject's finalize called
    Object for SoftReference is null
    After new byte[]: Soft Get = null
    [Full GC (System.gc()) [PSYoungGen: 59K->0K(1536K)] [ParOldGen: 4086K->4083K(4096K)] 4145K->4083K(5632K), [Metaspace: 3317K->3317K(1056768K)], 0.0036086 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
    Heap
     PSYoungGen      total 1536K, used 10K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
      eden space 1024K, 1% used [0x00000007bfe00000,0x00000007bfe02a68,0x00000007bff00000)
      from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
      to   space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
     ParOldGen       total 4096K, used 4083K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
      object space 4096K, 99% used [0x00000007bfa00000,0x00000007bfdfcfc0,0x00000007bfe00000)
     Metaspace       used 3324K, capacity 4564K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K
    

    2.2 案例2: 软引用缓存的使用

    public class BitMapManager {
        private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<>();
    
        // 保存Bitmap的软引用到HashMap
        public void saveBitmapToCache(String path) {
            // 强引用的Bitmap对象
            Bitmap bitmap = BitmapFactory.decodeFile(path);
            // 软引用的Bitmap对象
            SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
            // 添加该对象到Map中使其缓存
            imageCache.put(path, softBitmap);
            // 使用完后手动将位图对象置null
            bitmap = null;
        }
    
        public Bitmap getBitmapByPath(String path) {
            // 从缓存中取软引用的Bitmap对象
            SoftReference<Bitmap> softBitmap = imageCache.get(path);
            // 判断是否存在软引用
            if (softBitmap == null) {
                return null;
            }
            // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
            Bitmap bitmap = softBitmap.get();
            return bitmap;
        }
    }
    

    2.3 软引用的应用场景

    软引用主要用于内存敏感的高速缓存, 在Android系统中经常用到. 大多数 Android应用都会用到大量的图片. 由于读取文件需要硬件操作, 速度较慢, 所以考虑将图片缓存起来, 需要的时候直接从内存中读取.

    但由于图片占用内存空间比较大, 缓存很多图片就可能容易发生OutOfMemoryError, 这时我们可以考虑使用软引用技术来避免这个问题发生.

    SoftReference可以解决OOM的问题: 每一个对象通过软引用进行实例化, 这个对象就以cache的形式保存起来, 再次调用这个对象时, 就可以直接通过软引用中的get()方法得到对象中的资源数据. 当内存将要发生OOM的时候, GC会迅速把所有的软引用清除, 防止OOM发生.

    3 弱引用 (Weak Reference)

    弱引用用来描述非必须的对象, 它的强度比软引用更弱, 被弱引用关联的对象只能生存到下一次垃圾收集发生之前. 当垃圾收集器工作时, 无论当前内存是否足够, 都会回收掉只被弱引用关联的对象. 一旦一个弱引用对象被垃圾回收器回收, 便会加入到一个注册引用队列中.

    我们略微修改一下上一节案例1的代码:

    package com.healchow.java.detail;
    
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.lang.ref.WeakReference;
    
    public class WeakRefTest {
        private static ReferenceQueue<MyObject> weakQueue = new ReferenceQueue<>();
    
        private static class MyObject {
            @Override
            protected void finalize() throws Throwable {
                super.finalize();
                System.out.println("MyObject's finalize called");
            }
            @Override
            public String toString() {
                return "I am MyObject";
            }
        }
    
        private static class CheckRefQueue implements Runnable {
            Reference<MyObject> obj = null;
            @Override
            public void run() {
                try {
                    obj = (Reference<MyObject>)weakQueue.remove();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(obj != null) {
                    System.out.println("删除的弱引用为: " + obj);
                    System.out.println("获取弱引用的对象 obj.get() 为: " + obj.get());
                }
            }
        }
    
        public static void main(String[] args) {
            MyObject object = new MyObject();
            Reference<MyObject> weakRef = new WeakReference<>(object,weakQueue);
            System.out.println("创建的弱引用为: " + weakRef);
            new Thread(new CheckRefQueue()).start();
    
            object = null;
            System.out.println("Before GC: Weak Get = " + weakRef.get());
            System.gc();
            System.out.println("After GC: Weak Get = " + weakRef.get());
        }
    }
    

    (1) 演示方法 —— 不修改JVM参数:

    运行结果为:

    创建的弱引用为: java.lang.ref.WeakReference@6f94fa3e
    Before GC: Weak Get = I am MyObject
    After GC: Weak Get = null
    删除的弱引用 obj 为: java.lang.ref.WeakReference@6f94fa3e
    但是获取弱引用的对象 obj.get() 为: null
    MyObject's finalize called
    

    可以看到:

    在GC之前, 弱引用对象并未被垃圾回收器发现, 因此通过 weakRef.get()可以获取对应的对象引用.

    但是只要进行垃圾回收, 弱引用就会被发现, 并立即被回收, 并加入注册引用队列中. 此时再试图通过weakRef.get()获取对象的引用就会失败.

    (2) 弱引用的使用场景:

    弱引用的使用场景可参考java.util.WeakHashMap.

    软引用、弱引用都非常适合来保存那些可有可无的缓存数据. 系统内存不足时, 这些缓存数据会被回收, 就不会导致内存溢出. 而当内存资源充足时, 这些缓存数据又可以存在相当长的时间, 从而提高系统的响应速度用.

    4 虚引用 (Phantom Reference)

    虚引用也称为幽灵引用或者幻影引用, 它是最弱的一种引用关系. 一个持有虚引用的对象, 和没有引用几乎是一样的 —— 随时都有可能被垃圾回收器回收.

    当试图通过虚引用的get()方法取得强引用时, 总是会失败.

    并且虚引用必须和引用队列一起使用, 它的作用在于跟踪垃圾回收过程.

    虚引用中get()方法永远返回null, 其实现如下:

    public T get() {
        return null;
    }
    

    我们再来修改第2节案例1的代码:

    package com.healchow.java.detail;
    
    import java.lang.ref.PhantomReference;
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.util.concurrent.TimeUnit;
    
    public class PhantomRefTest {
        private static ReferenceQueue<MyObject> phantomQueue = new ReferenceQueue<>();
    
        private static class MyObject {
            @Override
            protected void finalize() throws Throwable {
                super.finalize();
                System.out.println("MyObject's finalize called");
            }
            @Override
            public String toString() {
                return "I am MyObject";
            }
        }
    
        private static class CheckRefQueue implements Runnable {
            Reference<MyObject> obj = null;
            @Override
            public void run() {
                try {
                    obj = (Reference<MyObject>) phantomQueue.remove();
                    System.out.println("删除的虚引用 obj 为: " + obj);
                    System.out.println("但是获取虚引用的对象 obj.get() 为: " + obj.get());
                    System.exit(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            MyObject object = new MyObject();
            Reference<MyObject> phantomRef = new PhantomReference<>(object, phantomQueue);
            System.out.println("创建的虚引用为:" + phantomRef);
            new Thread(new CheckRefQueue()).start();
    
            object = null;
            TimeUnit.SECONDS.sleep(1);
            int i = 1;
            while (true) {
                System.out.println("第" + i++ + "次gc");
                System.gc();
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    

    (1) 演示方法 —— 不修改JVM参数:

    运行结果为:

    创建的虚引用为:java.lang.ref.PhantomReference@6f94fa3e
    第1次gc
    MyObject's finalize called
    第2次gc
    删除的虚引用 obj 为: java.lang.ref.PhantomReference@6f94fa3e
    但是获取虚引用的对象 obj.get() 为: null
    

    可以看到:

    在经过一次GC之后, 系统找到了垃圾对象, 并调用finalize()方法回收内存, 但没有立即将虚引用对象加入回收队列.

    第二次GC时, 该对象真正被GC清除, 此时虚引用对象被加入虚引用队列.

    (2) 虚引用的使用场景:

    虚引用的最大作用在于跟踪对象回收, 清理被销毁对象的相关资源.

    通常当对象不被使用时, 重载该对象的类的finalize()方法可以回收对象的资源. 但是如果使用不慎, 会使得对象复活. 比如这样重写finalize()方法:

    public class Test {
        private static Test obj;
        
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            obj = this;
        }
    }
    

    创建Test对象: obj = new Test();, 然后令obj = null;, 之后调用System.gc()企图销毁该对象.

    但是很抱歉, 不管你调用多少次System.gc()都没有用, 除非再次obj = null;才能回收对象.

    原因: JVM对每一个对象最多只执行一次被重写的finalize()方法, 示例代码中, 在super.finalize()之后又对obj进行了赋值, 使得obj又复活了, 它重写的finalize()方法不会被调用第二次.

    (3) 通过虚引用清理对象

    上面的小片段说明重写finalize()方法并不是很靠谱, 我们可以使用虚引用来清理对象所占用的资源. 修改代码如下:

    package com.healchow.java.detail;
    
    import java.lang.ref.PhantomReference;
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    public class PhantomRefTest2 {
        private static ReferenceQueue<MyObject> phantomQueue = new ReferenceQueue<>();
        private static Map<Reference<MyObject>, String> resourceMap = new HashMap<>();
    
        private static class MyObject {
            @Override
            protected void finalize() throws Throwable {
                super.finalize();
                System.out.println("MyObject's finalize called");
            }
            @Override
            public String toString() {
                return "I am MyObject";
            }
        }
    
        private static class CheckRefQueue implements Runnable {
            Reference<MyObject> refObj = null;
    
            @Override
            public void run() {
                try {
                    refObj = (Reference<MyObject>) phantomQueue.remove();
                    // 从资源Map中移除弱引用对象, 即手动释放资源
                    Object value = resourceMap.get(refObj);
                    System.out.println("clean resource: " + value);
                    resourceMap.remove(refObj);
    
                    System.out.println("删除的虚引用为: " + refObj);
                    System.out.println("获取虚引用的对象 obj.get() 为: " + refObj.get());
                    System.exit(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            MyObject object = new MyObject();
            Reference<MyObject> phantomRef = new PhantomReference<>(object, phantomQueue);
            System.out.println("创建的虚引用为: " + phantomRef);
            new Thread(new CheckRefQueue()).start();
            // 将创建的虚引用对象存入资源Map
            resourceMap.put(phantomRef, "Some Resources");
    
            object = null;
            TimeUnit.SECONDS.sleep(1);
            int i = 1;
            while (true) {
                System.out.println("第" + i++ + "次gc");
                System.gc();
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    

    运行结果:

    创建的虚引用为: java.lang.ref.PhantomReference@6f94fa3e
    第1次gc
    MyObject's finalize called
    第2次gc
    clean resource: Some Resources
    删除的虚引用 obj 为: java.lang.ref.PhantomReference@6f94fa3e
    但是获取虚引用的对象 obj.get() 为: null
    

    参考资料

    《Java程序性能优化——让你的Java程序更快、更稳定》葛一鸣 等编著。

    《Java堆内存http://blog.csdn.net/u013256816/article/details/50764532

    版权声明

    本文版权归原作者所有, 如有侵权, 请联系博主, 定当立即删除.

    若要转载, 请在文章页面明显位置标明原始链接, 否则一切责任自负.

  • 相关阅读:
    18个功能强大的HTML5 和JavaScript游戏引擎库
    10 个超棒的 jQuery 视频插件
    HTML5播放视频音频
    CSS代码重构与优化之路
    推荐10款web前端的 HTML5 开发框架和开发工具
    JS中日期相关函数
    技术笔记1:java.sql.SQLException: Access denied for user 'root'@'localhost' (using password)
    图解HTTP阅读笔记(1)-网络基础TCP/IP
    框架和设计模式的区别
    SSH与MVC
  • 原文地址:https://www.cnblogs.com/shoufeng/p/11491392.html
Copyright © 2011-2022 走看看