欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-14 13:45:57
java 四种引用 java4种引用的级别由高到低依次为: strongreference  >  softreference  &...

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中应用详解,希望对大家有所帮助