zoukankan      html  css  js  c++  java
  • java四种引用及在LeakCanery中应用

    java 四种引用

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

    StrongReference  >  SoftReference  >  WeakReference  >  PhantomReference
    

    1. StrongReference

    String tag = new String("T");   
    

    此处的 tag 引用就称之为强引用。而强引用有以下特征:

    1. 强引用可以直接访问目标对象。
    2. 强引用所指向的对象在任何时候都不会被系统回收。
    3. 强引用可能导致内存泄漏。
    

    我们要讨论的其它三种Reference较之于强引用而言都属于“弱引用”,也就是他们所引用的对象只要没有强引用,就会根据条件被JVM的垃圾回收器所回收,它们被回收的时机以及用法各不相同。下面分别来进行讨论。

    2. SoftReference

    软引用有以下特征:

    1. 软引用使用 get() 方法取得对象的强引用从而访问目标对象。
    2. 软引用所指向的对象按照 JVM 的使用情况(Heap 内存是否临近阈值)来决定是否回收。
    3. 软引用可以避免 Heap 内存不足所导致的异常。
    

    当垃圾回收器决定对其回收时,会先清空它的 SoftReference,也就是说 SoftReference 的 get() 方法将会返回 null,然后再调用对象的 finalize() 方法,并在下一轮 GC 中对其真正进行回收。

    3. WeakReference

    WeakReference 是弱于 SoftReference 的引用类型。弱引用的特性和基本与软引用相似,区别就在于弱引用所指向的对象只要进行系统垃圾回收,不管内存使用情况如何,永远对其进行回收(get() 方法返回 null)。

    弱引用有以下特征:

    1. 弱引用使用 get() 方法取得对象的强引用从而访问目标对象。
    2. 一旦系统内存回收,无论内存是否紧张,弱引用指向的对象都会被回收。
    3. 弱引用也可以避免 Heap 内存不足所导致的异常。
    

    4. PhantomReference(虚引用)

    PhantomReference 是所有“弱引用”中最弱的引用类型。不同于软引用和弱引用,虚引用无法通过get()方法来取得目标对象的强引用从而使用目标对象,观察源码可以发现 get() 被重写为永远返回 null。

    虚引用有以下特征:

    虚引用永远无法使用 get() 方法取得对象的强引用从而访问目标对象。
    虚引用所指向的对象在被系统内存回收前,虚引用自身会被放入 ReferenceQueue 对象中从而跟踪对象垃圾回收。
    虚引用不会根据内存情况自动回收目标对象。
    虚引用必须和引用队列(ReferenceQueue)联合使用
    

    Reference与ReferenceQueue 使用demo

    定义一个对象Brain

    public class Brain  {
    
        public int mIndex;
        // 占用较多内存,当系统内存不足时,会自动进行回收
        private byte []mem;
    
        public Brain(int index) {
            mIndex = index;
            mem = new byte[1024 * 1024];
        }
        
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            LogUtils.e("Brain", "finalize + index=" + mIndex);
        }
    }
    

    创建Reference并添加到RefrenceQueue中

    // 定义几个成员变量
    ReferenceQueue<Brain> mWeakQueue = new ReferenceQueue<>();
    ReferenceQueue<Brain> mPhQueue = new ReferenceQueue<>();
    
    List<WeakReference<Brain>> mWeakList = new ArrayList<>();
    List<PhantomReference<Brain>> mPhList = new ArrayList<>();
    
    
    // 开启守护线程用于检测ReferenceQue中是否有对象被添加
    private void startDemoThread() {
        Thread threadWeak = new Thread(() -> {
                try {
                    int cnt = 0;
                    WeakReference<Brain> k;
                    // remove 方法为阻塞式的, 而poll方法不是
                    while((k = (WeakReference) mWeakQueue.remove()) != null) {
                        LogUtils.e(TAG, "回收了WeakReference指向对象, : cnt=" + (cnt++) + " wf=" + k);
                    }
                } catch(InterruptedException e) {
                    //结束循环
                }
            }, "MainActivityWeak");
            threadWeak.setDaemon(true);
            threadWeak.start();
    
            Thread threadPh = new Thread(() -> {
                try {
                    int cnt = 0;
                    PhantomReference<Brain> k;
                    while((k = (PhantomReference) mPhQueue.remove()) != null) {
                        LogUtils.e(TAG, "回收了PhantomReference指向对象, cnt=" + (cnt++) + "   pr=" + k);
                    }
                } catch(InterruptedException e) {
                    //结束循环
                }
            }, "MainActivityPhantom");
            threadPh.setDaemon(true);
            threadPh.start();
    }
     
    // 注意创建的Reference对象需要暂存起来(实测中,如果Reference被回收是不会被添加到ReferenceQueue中的)
    private void test() {
        for (int i=0; i<1000; i++) {
                        Brain src1 = new Brain(i);
                        Brain src2 = new Brain(100000 + i);
                        // 将Reference注册到RefrenceQueue中
                        WeakReference<Brain> weakReference = new WeakReference<Brain>(src1, mWeakQueue);
                        mWeakList.add(weakReference);
                        
                        //将Reference注册到RefrenceQueue中
                        PhantomReference<Brain> phantomReference = new PhantomReference<>(src2, mPhQueue);
                        mPhList.add(phantomReference);
    
    
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    }
        
    

    结果打印:

    2019-01-29 19:22:27.499 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=0 wf=java.lang.ref.WeakReference@e1f904c
    2019-01-29 19:22:27.499 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=1 wf=java.lang.ref.WeakReference@82fc895
    2019-01-29 19:22:27.500 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=2 wf=java.lang.ref.WeakReference@3b3fdaa
    2019-01-29 19:22:27.500 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=3 wf=java.lang.ref.WeakReference@668fd9b
    2019-01-29 19:22:27.501 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=0
    2019-01-29 19:22:27.501 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100000
    2019-01-29 19:22:27.502 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=4 wf=java.lang.ref.WeakReference@8db6538
    2019-01-29 19:22:27.502 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=5 wf=java.lang.ref.WeakReference@f915911
    2019-01-29 19:22:27.503 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=1
    2019-01-29 19:22:27.503 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100001
    2019-01-29 19:22:27.504 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=2
    2019-01-29 19:22:27.505 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100002
    2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=3
    2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100003
    2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=4
    2019-01-29 19:22:27.508 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100004
    2019-01-29 19:22:27.508 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=5
    2019-01-29 19:22:27.509 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100005
    2019-01-29 19:22:27.629 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=0   pr=null
    2019-01-29 19:22:27.629 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=1   pr=null
    2019-01-29 19:22:27.629 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=6 wf=java.lang.ref.WeakReference@e2c4a76
    2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=2   pr=null
    2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=7 wf=java.lang.ref.WeakReference@4cfd877
    2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=3   pr=null
    2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=4   pr=null
    2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=8 wf=java.lang.ref.WeakReference@37d9ce4
    2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=5   pr=null
    2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=9 wf=java.lang.ref.WeakReference@ea1754d
    

    结果分析:

    1. 当对象被回收后,持有他的引用WeakReference/PhantomReference会被放入ReferenceQueue中
    2. WeakReference在原始对象回收之前被放入ReferenceQueue中,而PhantomReference是在回收之后放入ReferenceQueue中

    WeakReference在Leakcanery中的应用

    LeakCanery是Android检测内存泄漏的工具,可以检测到Activity/Fragment存在的内存泄漏。

    检测原理:

    1. 在Application中注册监听所有Activity生命周期的listener,registerActivityLifecycleCallbacks。
    //ActivityRefWatcher 中的代码
    public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    
      public void stopWatchingActivities() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    
    1. 当Activity的onDestroy被调用时,生成一个uuid,标记这个Activity的WeakReference。
    2. 创建一个弱引用,并与一个跟踪所有activit回收的ReferenceQueue相关联。(放入一个map中,key : uuid, value:weakReference)
    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
          new ActivityLifecycleCallbacksAdapter() {
            @Override public void onActivityDestroyed(Activity activity) {
              refWatcher.watch(activity);
            }
          };
    

    具体的watch执行如下:

    public void watch(Object watchedReference, String referenceName) {
        if (this == DISABLED) {
          return;
        }
        checkNotNull(watchedReference, "watchedReference");
        checkNotNull(referenceName, "referenceName");
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
    
        ensureGoneAsync(watchStartNanoTime, reference);
      }
    

    ensureGoneAsync执行如下:

    // watchExecutor 在一定时间后检查被注册的WeakReference有没有被添加到ReferenceQueue中
    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        watchExecutor.execute(new Retryable() {
          @Override public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
          }
        });
      }
    
    1. 在onDestry被调用后若干秒执行如下操作:到ReferenceQueue中去取这个Activity,如果能够取到说明这个Activity被正常回收了。如果无法回收,触发GC,再去RerenceQueue中取如果还是无法取到,说明Activity没有被系统回收,可能存在内存泄漏。
      真正核心的代码如下:
    long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    
        // 如果ReferenceQue中有activity的弱引用,则将retainedKeys中的uuid移除
        removeWeaklyReachableReferences();
    
        if (debuggerControl.isDebuggerAttached()) {
          // The debugger can create false leaks.
          return RETRY;
        }
        
        // 如果activity对应的uuid已经被移除,说明activity已经被回收,无内存泄漏
        if (gone(reference)) {
          return DONE;
        }
        
        // 触发gc,进行垃圾回收
        gcTrigger.runGc();
        removeWeaklyReachableReferences();
        
        // 如果uuid还没有被移除,说明activiy存在内存泄漏,需要dump内存,进行分析
        if (!gone(reference)) {
          long startDumpHeap = System.nanoTime();
          long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
    
          File heapDumpFile = heapDumper.dumpHeap();
          if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
          }
          long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
    
          HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
              .referenceName(reference.name)
              .watchDurationMs(watchDurationMs)
              .gcDurationMs(gcDurationMs)
              .heapDumpDurationMs(heapDumpDurationMs)
              .build();
    
          heapdumpListener.analyze(heapDump);
        }
        return DONE;
      }
    

    HeapDump dump内存和分析的过程这里就不细说。

    WeakReference在ThreadLocal中应用

    我们知道ThreadLocal是用来存放当前线程值的一个类,也是线程同步的一个工具。
    顺便介绍下它的原理,首先我们得知道Thread对象中有一个ThreadLocalMap的成员变量,存放当前线程的所有ThreadLocal值。
    该map中存放的数据类型如下:

    static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    当我们调用new ThreadLocal().set(5)时, 首先会获取到当前线程的ThreadLocalMap对象,然后通过自身作为key值到获取value。
    如果这里没有使用弱引用,那么ThreadLocal对象很可能无法释放。

    WeakReference在Handler中使用防止内存泄漏

    熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。
    下面使用弱应用方式创建handler:

    public class MainActivity extends AppCompatActivity {
    
        private Handler mHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler = new MyHandler(this);
            start();
        }
    
        private void start() {
            Message msg = Message.obtain();
            msg.what = 1;
            mHandler.sendMessage(msg);
        }
    
        private static class MyHandler extends Handler {
    
            private WeakReference<MainActivity> activityWeakReference;
    
            public MyHandler(MainActivity activity) {
                activityWeakReference = new WeakReference<>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = activityWeakReference.get();
                if (activity != null) {
                    if (msg.what == 1) {
                        // 做相应逻辑
                    }
                }
            }
        }
    }
    
  • 相关阅读:
    互联网创业的葵花宝典
    null和undefined的区别
    mpc0.9编译方法
    gmp5.0.5编译
    为iphone及iphone simulator编译poco库
    binutils2.22编译心得
    为iphone及iphone simulator编译qt库
    sql server之触发器调用C#CLR存储过程实现两个表的数据同步
    poco之HttpRequest之get方法
    poco之HttpRequest之post方法
  • 原文地址:https://www.cnblogs.com/NeilZhang/p/11441402.html
Copyright © 2011-2022 走看看