Java/Android引用类型及其使用全面分析

Java/Android中有四种引用类型,分别是:

Strong reference - 强引用
Soft Reference - 软引用
Weak Reference - 弱引用
Phantom Reference - 虚引用

不同的引用类型有着不同的特性,同时也对应着不同的使用场景。

1.Strong reference - 强引用

实际编码中最常见的一种引用类型。常见形式如:A a = new A();等。强引用本身存储在栈内存中,其存储指向对内存中对象的地址。一般情况下,当对内存中的对象不再有任何强引用指向它时,垃圾回收机器开始考虑可能要对此内存进行的垃圾回收。如当进行编码:a = null,此时,刚刚在堆中分配地址并新建的a对象没有其他的任何引用,当系统进行垃圾回收时,堆内存将被垃圾回收。

SoftReference、WeakReference、PhantomReference都是类java.lang.ref.Reference的子类。Reference作为抽象基类,定义了其子类对象的基本操作。Reference子类都具有如下特点:

1.Reference子类不能无参化直接创建,必须至少以强引用对象为构造参数,创建各自的子类对象;
2.因为1中以强引用对象为构造参数创建对象,因此,使得原本强引用所指向的堆内存中的对象将不再只与强引用本身直接关联,与Reference的子类对象的引用也有一定联系。且此种联系将可能影响到对象的垃圾回收。

根据不同的子类对象对其指示对象(强引用所指向的堆内存中的对象)的垃圾回收不同的影响特点,分别形成了三个子类,即SoftReference、WeakReference和PhantomReference。

2.Soft Reference - 软引用

软引用的一般使用形式如下:
A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);

通过对象的强引用为参数,创建了一个SoftReference对象,并使栈内存中的wrA指向此对象。

此时,进行如下编码:a = null,对于原本a所指向的A对象的垃圾回收有什么影响呢?

先直接看一下下面一段程序的输出结果:

import java.lang.ref.SoftReference;

public class ReferenceTest {

  public static void main(String[] args) {

    A a = new A();

    SoftReference<A> srA = new SoftReference<A>(a);

    a = null;

    if (srA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + srA.get());
    }

    // 垃圾回收
    System.gc();

    if (srA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + srA.get());
    }

  }
}

class A {

}

##输出结果为:

1 a对象尚未被回收A@4807ccf6
2 a对象尚未被回收A@4807ccf6

当 a = null后,堆内存中的A对象将不再有任何的强引用指向它,但此时尚存在srA引用的对象指向A对象。当第一次调用srA.get()方法返回此指示对象时,由于垃圾回收器很有可能尚未进行垃圾回收,此时get()是有结果的,这个很好理解。当程序执行System.gc();强制垃圾回收后,通过srA.get(),发现依然可以得到所指示的A对象,说明A对象并未被垃圾回收。那么,软引用所指示的对象什么时候才开始被垃圾回收呢?需要满足如下两个条件:

1.当其指示的对象没有任何强引用对象指向它;

2.当虚拟机内存不足时。

因此,SoftReference变相的延长了其指示对象占据堆内存的时间,直到虚拟机内存不足时垃圾回收器才回收此堆内存空间。

3.Weak Reference - 弱引用

同样的,软引用的一般使用形式如下:

A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);

当没有任何强引用指向此对象时, 其垃圾回收又具有什么特性呢?

import java.lang.ref.WeakReference;

public class ReferenceTest {

  public static void main(String[] args) {

    A a = new A();

    WeakReference<A> wrA = new WeakReference<A>(a);

    a = null;

    if (wrA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + wrA.get());
    }

    // 垃圾回收
    System.gc();

    if (wrA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + wrA.get());
    }

  }

}

class A {

}

##输出结果为:

a对象尚未被回收A@52e5376a
a对象进入垃圾回收流程

输出的第一条结果解释同上。当进行垃圾回收后,wrA.get()将返回null,表明其指示对象进入到了垃圾回收过程中。因此,对弱引用特点总结为:

WeakReference不改变原有强引用对象的垃圾回收时机,一旦其指示对象没有任何强引用对象时,此对象即进入正常的垃圾回收流程。

那么,依据此特点,很可能有疑问:WeakReference存在又有什么意义呢?

其主要使用场景见于:当前已有强引用指向强引用对象,此时由于业务需要,需要增加对此对象的引用,同时又不希望改变此引用的垃圾回收时机,此时WeakReference正好符合需求,常见于一些与生命周期的场景中。

下面给出一个Android中关于WeakReference使用的场景 —— 结合静态内部类和WeakReference来解决Activity中可能存在的Handler内存泄露问题。

Activity中我们需要新建一个线程获取数据,使用handler - sendMessage方式。下面是这一过程的一般性代码:

public class MainActivity extends Activity {

  //...
  private int page;
  private Handler handler = new Handler() {

    @Override
    public void handleMessage(Message msg) {
      if (msg.what == 1) {

        //...

        page++;
      } else {

        //...

      }

    };
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //...

    new Thread(new Runnable() {
      @Override
      public void run() {
        //..
        Message msg = Message.obtain();
        msg.what = 1;
        //msg.obj = xx;
        handler.sendMessage(msg);
      }
    }).start();

    //...

  }

}

在Eclispe中Run Link,将会看到警示信息:This Handler class should be static or leaks might occur ...点击查看此信息,其详情中对问题进行了说明并给出了建议性的解决方案。

Issue: Ensures that Handler classes do not hold on to a reference to an outer class
Id: HandlerLeak

Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class;In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

大致的意思是建议将Handler定义成内部静态类,并在此静态内部类中定义一个WeakReference的引用,由于指示外部的Activity对象。

问题分析:

Activity具有自身的生命周期,Activity中新开启的线程运行过程中,可能此时用户按下了Back键,或系统内存不足等希望回收此Activity,由于Activity中新起的线程并不会遵循Activity本身的什么周期,也就是说,当Activity执行了onDestroy,由于线程以及Handler 的HandleMessage的存在,使得系统本希望进行此Activity内存回收不能实现,因为非静态内部类中隐性的持有对外部类的引用,导致可能存在的内存泄露问题。

因此,在Activity中使用Handler时,一方面需要将其定义为静态内部类形式,这样可以使其与外部类(Activity)解耦,不再持有外部类的引用,同时由于Handler中的handlerMessage一般都会多少需要访问或修改Activity的属性,此时,需要在Handler内部定义指向此Activity的WeakReference,使其不会影响到Activity的内存回收同时,可以在正常情况下访问到Activity的属性。

Google官方给出的建议写法为:

public class MainActivity extends Activity {

  //...
  private int page;
  private MyHandler mMyHandler = new MyHandler(this);

  private static class MyHandler extends Handler {

    private WeakReference<MainActivity> wrActivity;

    public MyHandler(MainActivity activity) {
      this.wrActivity = new WeakReference<MainActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      if (wrActivity.get() == null) {
        return;
      }
      MainActivity mActivity = wrActivity.get();
      if (msg.what == 1) {

        //...
        mActivity.page++;

      } else {

        //...

      }
    }

  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //...

    new Thread(new Runnable() {
      @Override
      public void run() {
        //..
        Message msg = Message.obtain();
        msg.what = 1;
        //msg.obj = xx;
        mMyHandler.sendMessage(msg);
      }
    }).start();

    //...

  }

}

对于SoftReference和WeakReference,还有一个构造器参数为ReferenceQueue<T>,当SoftReference或WeakReference所指示的对象确实被垃圾回收后,其引用将被放置于ReferenceQueue中。注意上文中,当SoftReference或WeakReference的get()方法返回null时,仅是表明其指示的对象已经进入垃圾回收流程,此时对象不一定已经被垃圾回收。而只有确认被垃圾回收后,如果ReferenceQueue,其引用才会被放置于ReferenceQueue中。

看下面的一个例子:

public class ReferenceTest {

  public static void main(String[] args) {

    A a = new A();

    WeakReference<A> wrA = new WeakReference<A>(a);

    a = null;

    if (wrA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + wrA.get());
    }

    // 垃圾回收
    System.gc();

    if (wrA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + wrA.get());
    }

  }
}

class A {

  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    System.out.println("in A finalize");
  }

}

##输出结果为:

1 a对象尚未被回收A@46993aaa
2 a对象被回收
3 in A finalize

由此,也验证了上文中的“进入垃圾回收流程”的说法。下面结合ReferenceQueue,看一段代码:

public class ReferenceTest {

  public static void main(String[] args) {

    A a = new A();

    ReferenceQueue<A> rq = new ReferenceQueue<A>();
    WeakReference<A> wrA = new WeakReference<A>(a, rq);

    a = null;

    if (wrA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + wrA.get());
    }

    System.out.println("rq item:" + rq.poll());

    // 垃圾回收
    System.gc();

    if (wrA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + wrA.get());
    }

    /*
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    */

    System.out.println("rq item:" + rq.poll());

  }
}

class A {

  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    System.out.println("in A finalize");
  }

}

##输出结果为:

a对象尚未被回收A@302b2c81
rq item:null
a对象进入垃圾回收流程
rq item:null
in A finalize

由此,验证了“仅进入垃圾回收流程的SoftReference或WeakReference引用尚未被加入到ReferenceQueue”。


public class ReferenceTest {

  public static void main(String[] args) {

    A a = new A();

    ReferenceQueue<A> rq = new ReferenceQueue<A>();
    WeakReference<A> wrA = new WeakReference<A>(a, rq);

    a = null;

    if (wrA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + wrA.get());
    }

    System.out.println("rq item:" + rq.poll());

    // 垃圾回收
    System.gc();

    if (wrA.get() == null) {
      System.out.println("a对象进入垃圾回收流程");
    } else {
      System.out.println("a对象尚未被回收" + wrA.get());
    }

    try {
      Thread.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    System.out.println("rq item:" + rq.poll());

  }
}

class A {

  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    System.out.println("in A finalize");
  }

}

##输出结果为:

a对象尚未被回收A@6276e1db
rq item:null
a对象进入垃圾回收流程
in A finalize
rq item:java.lang.ref.WeakReference@645064f

由此,证实了上述说法。

4.PhantomReference

与SoftReference或WeakReference相比,PhantomReference主要差别体现在如下几点:

1.PhantomReference只有一个构造函数PhantomReference(T referent, ReferenceQueue<? super T> q),因此,PhantomReference使用必须结合ReferenceQueue;

2.不管有无强引用指向PhantomReference的指示对象,PhantomReference的get()方法返回结果都是null。

public class ReferenceTest {

  public static void main(String[] args) {

    A a = new A();

    ReferenceQueue<A> rq = new ReferenceQueue<A>();
    PhantomReference<A> prA = new PhantomReference<A>(a, rq);

    System.out.println("prA.get():" + prA.get());

    a = null;

    System.gc();

    try {
      Thread.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    System.out.println("rq item:" + rq.poll());

  }
}

class A {

}

##输出结果为:

prA.get():null
rq item:java.lang.ref.PhantomReference@1da12fc0

代码中的Thread.sleep(1);作用与上例中相同,都是确保垃圾回收线程能够执行。否则,进进入垃圾回收流程而没有真正被垃圾回收的指示对象的虚引用是不会被加入到PhantomReference中的。

与WeakReference相同,PhantomReference并不会改变其指示对象的垃圾回收时机。且可以总结出:ReferenceQueue的作用主要是用于监听SoftReference/WeakReference/PhantomReference的指示对象是否已经被垃圾回收。

以上就是小编为大家带来的Java/Android引用类型及其使用全面分析的全部内容了,希望对大家有所帮助,多多支持我们~

(0)

相关推荐

  • 全面解析Java中的引用类型

    如果一个内存中的对象没有任何引用的话,就说明这个对象已经不再被使用了,从而可以成为被垃圾回收的候选.不过由于垃圾回收器的运行时间不确定,可被垃圾回收的对象的实际被回收时间是不确定的.对于一个对象来说,只要有引用的存在,它就会一直存在于内存中.如果这样的对象越来越多,超出了JVM中的内存总数,JVM就会抛出OutOfMemory错误.虽然垃圾回收的具体运行是由JVM来控制的,但是开发人员仍然可以在一定程度上与垃圾回收器进行交互,其目的在于更好的帮助垃圾回收器管理好应用的内存.这种交互方式就是使用J

  • 再谈Javascript中的基本类型和引用类型(推荐)

    一.基本类型和引用类型概述 js中数据类型的值包括:基本类型值和引用类型值 基本数据类型:undefined;null;boolean;number;string 引用类型值:保存在内存中,js不允许直接访问内存位置,因此时操作引用而不是实际对象 二.如何检测数据类型 1.基本数据类型的检测:使用typeof var s = "AAA"; alert(typeof s); //返回string 2.引用类型(对象类型)检测:使用instanceof alert(person insta

  • Java/Android引用类型及其使用全面分析

    Java/Android中有四种引用类型,分别是: Strong reference - 强引用 Soft Reference - 软引用 Weak Reference - 弱引用 Phantom Reference - 虚引用 不同的引用类型有着不同的特性,同时也对应着不同的使用场景. 1.Strong reference - 强引用 实际编码中最常见的一种引用类型.常见形式如:A a = new A();等.强引用本身存储在栈内存中,其存储指向对内存中对象的地址.一般情况下,当对内存中的对象

  • 一种c#深拷贝方式完胜java深拷贝(实现上的对比分析)

    楼主是一名asp.net攻城狮,最近经常跑java组客串帮忙开发,所以最近对java的一些基础知识特别上心.却遇到需要将一个对象深拷贝出来做其他事情,而原对象保持原有状态的情况.(实在是不想自己new一个出来,然后对着一堆字段赋值......好吧,再此之前我没有关心是否项目框架有深拷贝的方法),然后就想着用反射实现吧....接下来 是我自己的原因,还是真的不存在这样的纯用反射实现的深拷贝方式....(c#是有纯反射实现的) 但也不能算自己白忙活吧,也找到了其他实现深拷贝的方式(但是每种方式我都觉

  • Android Beam 文件传输失败分析与解决方法

    最近在修改Android7.0原生平台的一些bug,其中有关Android Beam传输文件的一些问题还是蛮多的.所以特地找时间总结下曾经踏过的坑. 1.传输的文件名包含中文时,导致传输失败 可能是由于Google未考虑到本地化差异,导致在传输中文文件名的文件时直接提示传输失败. packages\apps\Nfc\src\com\android\nfc\beam\MimeTypeUtil.java 其实,上面忘了说了,只是从文件管理器中进入Android Beam分享才会出现上面的问题.因为当

  • Android Intent传递数据底层分析详细介绍

    Android  Intent传递数据底层分析详细介绍 我们知道在Activity切换时,如果需要向下一个ActivityB传递数据,可以借助Intent对象的putExtra方法. 但是不知各位有没有想过这样一个问题:ActivityB中获取到的对象跟上一个Activity中的那个对象有什么关系? 换句话说就是,我在ActivityB中通过Intent获取的对象跟ActivityA中的那个对象,有没有可能是同一个对象? 按照常理来说,博主提出一个设想后续的就是证明过程了,但是我要遗憾的告诉你,

  • Android代码块执行顺序分析总结

    本文介绍了Android代码块执行顺序分析总结,分享给大家,具体如下: 子类.父类变量,代码块,以及构造方法的加载顺序,是我们在程序开发中时不时遇到的一个问题,也是比较容易混淆的. 基础准备: 变量: 成员变量(叫类变量.实例变量)和局部变量 方法: 成员方法和构造方法 代码块:普通代码块.构造代码块.静态代码块.同步代码块 测试的父类: public class ParentClass { public static String name = "爸爸"; public int ag

  • 详解Java的引用类型及使用场景

    每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过"引用".在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference).软引用(Soft Reference).弱引用(Weak Reference).虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱,今天这篇文章就简单介绍一下这四种类型,并简单说一下他们的使用场景. 1. 强引用(Strong

  • Android  Intent传递数据底层分析详细介绍

    Android  Intent传递数据底层分析详细介绍 我们知道在Activity切换时,如果需要向下一个ActivityB传递数据,可以借助Intent对象的putExtra方法. 但是不知各位有没有想过这样一个问题:ActivityB中获取到的对象跟上一个Activity中的那个对象有什么关系? 换句话说就是,我在ActivityB中通过Intent获取的对象跟ActivityA中的那个对象,有没有可能是同一个对象? 按照常理来说,博主提出一个设想后续的就是证明过程了,但是我要遗憾的告诉你,

  • Android开发Retrofit源码分析

    目录 项目结构 retrofit 使用 Retrofit #create ServiceMethod #parseAnnotations HttpServiceMethod#parseAnnotations 第二种 非Kotlin协程情况 DefaultCallAdapterFactory#get 第一种 Kotlin协程情况 总结 项目结构 把源码 clone 下来 , 可以看到 retrofit 整体结构如下 图 http包目录下就是一些http协议常用接口 , 比如 请求方法 url ,

  • Android View滑动的实现分析示例

    目录 1.layout方法 2.offsetLeftAndRight()与offsetTopAndBottom() 3.LayoutParams(改变布局参数) 4.scrollTo与scrollBy 5.Scroller 实现View滑动有很多种方法,这篇帖子介绍6中滑动的方法,分别是: layout().offsetLeftAndRight().offsetTopAndBottom().LayoutParams.scrollTo.scrollBy.Scroller. 1.layout方法 绘

  • Android AccessibilityService 事件分发原理分析总结

    目录 AccessibilityService 监听事件的调用逻辑 onAccessibilityEvent onIntercept AccessibilityService 事件的外部来源 AccessibilityServiceInfo AccessibilityManager AccessibilityManagerService AccessibilityServiceConnection 前言: 在了解了无障碍服务基础使用之后,我们来探究一下 AccessibilityService

随机推荐