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中

结果打印:

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

结果分析:

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

WeakReference在Leakcanery中的应用

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

检测原理:

在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);
 }

当Activity的onDestroy被调用时,生成一个uuid,标记这个Activity的WeakReference。
创建一个弱引用,并与一个跟踪所有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);
   }
  });
 }

在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内存和分析的过程这里就不细说。

总结

以上所述是小编给大家介绍的java四种引用及在LeakCanery中应用详解,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

(0)

相关推荐

  • Java中四种引用类型详细介绍

    Java 四种引用类型 对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有对象处于可触及(reachable)状态,程序才能使用它.从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期.这4种级别由高到低依次为:强引用.软引用.弱引用和虚引用. ⑴强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,

  • 详解Java的四种引用方式及其区别

    java内存管理分为内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看对象是否有引用指向该对象. java对象的引用包括 强引用,软引用,弱引用,虚引用 Java中提供这四种引用类型主要有两个目的: 第一是可以让程序员通过代码的方式决定某些对象的生命周期: 第二是有利于JVM进行垃圾回收. 下面来阐述一下这四种类型引用的概念: 1.强引用 是指创建一个对象并把这个对象赋给一个引用变量. 比如: Object object =new Object(); String str ="hel

  • 浅谈Java中的四种引用方式的区别

    强引用.软引用.弱引用.虚引用的概念 强引用(StrongReference) 强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用: Object object = new Object(); String str = "hello"; 只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象. 比如下面这段代码: public class Main { publi

  • Java对象的四种引用方式实例分析

    本文实例讲述了Java对象的四种引用方式.分享给大家供大家参考,具体如下: 一 点睛 Java语言对对象的引用有如下四种方式 强引用:我们平时一般都是这种引用,当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收. 软引用:软引用需要通过SoftReference类来实现,当一个对象只具有软引用时,它有可能被垃圾回收机制回收.对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可以使用该对象,当系统内存空间不够时,系统可能回收它.软引用通

  • java四种引用及在LeakCanery中应用详解

    java 四种引用 Java4种引用的级别由高到低依次为: StrongReference  >  SoftReference  >  WeakReference  >  PhantomReference 1. StrongReference String tag = new String("T"); 此处的 tag 引用就称之为强引用.而强引用有以下特征: 1. 强引用可以直接访问目标对象. 2. 强引用所指向的对象在任何时候都不会被系统回收. 3. 强引用可能导致

  • Java四个线程常用函数超全使用详解

    目录 前言 1. wait() 2. join() 3. sleep() 4. yield() 5. 总结 5.1 wait和join的区别 5.2 wait和sleep的区别 前言 之前没怎么关注到这两个的区别以及源码探讨 后面被某个公司面试问到了,开始查漏补缺 1. wait() 使当前线程等待,直到它被唤醒,通常是通过被通知或被中断,或者直到经过一定的实时时间. 本身属于一个Object 类,查看源代码也可知:public class Object { 查看其源码可知,一共有三个重载的方法

  • java 三种将list转换为map的方法详解

    java 三种将list转换为map的方法详解 在本文中,介绍三种将list转换为map的方法: 1) 传统方法 假设有某个类如下 class Movie { private Integer rank; private String description; public Movie(Integer rank, String description) { super(); this.rank = rank; this.description = description; } public Int

  • Java的四种引用方式

    目录 1.强引用(StrongReference) 2.软引用(SoftReference) 3.弱引用(WeakReference) 4.虚引用(PhantomReference) 5. 引用队列(ReferenceQueue) 1.强引用(StrongReference) 使用最普遍的引用. 只要引用链没有断开,强引用就不会断开.- 当内存空间不足,抛出OutOfMemoryError终止程序也不会回收具有强引用的对象. 通过将对象设置为null来弱化引用,使其被回收 Object obje

  • 分享JVM 的四种引用方式

    目录 前言 一.强引用 二.软引用 三.弱引用 四.虚引用 前言 Java中提供这四种引用类型主要有两个目的: 可以让程序员通过代码的方式决定某些对象的生命周期: 有利于JVM进行垃圾回收 java.lang.ref包下的引用类结构图: 一.强引用 特点:GC时,永远不会被回收 是指创建一个对象并把这个对象赋给一个引用变量. 比如: Object object = new Object(); String str = "hello" 强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿

  • JAVA四种基本排序方法实例总结

    本文实例讲述了JAVA四种基本排序方法.分享给大家供大家参考.具体如下: JAVA四种基本排序,包括冒泡法,插入法,选择法,SHELL排序法.其中选择法是冒泡法的改进,SHELL排序法是 插入法的改进.所以从根本上来说可以归纳为两种不同的排序方法:即:插入法&冒泡法 一 插入法: 遍历排序集合,每到一个元素时,都要将这个元素与所有它之前的元素遍历比较一遍,让符合排序顺序的元素挨个移动到当前范围内它最应该出现的位置.交换是相邻遍历移动,双重循环控制实现.这种排序法属于地头蛇类型,在我的地牌上我要把

  • Java 四种基本加密算法分析

    Java 四种基本加密算法分析 简单的java加密算法有: BASE64 严格地说,属于编码格式,而非加密算法 MD5(Message Digest algorithm 5,信息摘要算法) SHA(Secure Hash Algorithm,安全散列算法) HMAC(Hash Message Authentication Code,散列消息鉴别码) 1. BASE64 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045-RFC2049,上面有MIME的

  • Java中的引用和动态代理的实现详解

    我们知道,动态代理(这里指JDK的动态代理)与静态代理的区别在于,其真实的代理类是动态生成的.但具体是怎么生成,生成的代理类包含了哪些内容,以什么形式存在,它为什么一定要以接口为基础? 如果去看动态代理的源代码(java.lang.reflect.Proxy),会发现其原理很简单(真正二进制类文件的生成是在本地方法中完成,源代码中没有),但其中用到了一个缓冲类java.lang.reflect.WeakCache<ClassLoader,Class<?>[],Class<?>

随机推荐