基本类型与引用类型、强引用和弱引用

Java中数据类型可分为两大类:引用类型(reference)和基本类型(primitive)。

1)基本类型有8种:boolean、char、byte、short、int、long、float、double。

  它们都有相应的包装类,这些包装类都属于引用类型,它们依次是:Boolean、Character、Byte、Short、Integer、Long、Float、Double。

2)引用类型:所有Java对象都是通过对象引用(Object References)进行访问的,类似于C++中的指针,这个引用指向堆heap中的某块区域,实际的对象存在于heap中。例如,声明如下代码:

int prim = 10;
Integer refer = new Integer(10);

这两者在内存中的布局如下图所示:

 

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

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对象的垃圾回收有什么影响呢?

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

那么,软引用所指示的对象什么时候才开始被垃圾回收呢?需要满足如下两个条件:

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

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

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

 3. Weak Reference - 弱引用

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

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

 

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

  当 a = null 后,堆内存中的A对象将不再有任何的强引用指向它,但此时尚存在wrA引用的对象指向A对象。当第一次调用srA.get()方法返回此指示对象时,由于垃圾回收器很有可能尚未进行垃圾回收,此时get()是有结果的;当进行垃圾回收后,wrA.get()将返回null,表明其指示对象进入到了垃圾回收过程中。因此,对弱引用特点总结为:

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

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

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

例如:结合静态内部类和WeakReference来解决Activity中可能存在的Handler内存泄露问题。

  定义Handler时常会收到一个警告信息:This Handler class should be static or leaks might occur ...

  大致的意思是建议将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的属性。

4.PhantomReference

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

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

  • PhantomReference只有一个构造函数PhantomReference(T referent, ReferenceQueue<? super T> q),因此,PhantomReference使用必须结合ReferenceQueue
  • 不管有无强引用指向PhantomReference的指示对象,PhantomReference的get()方法返回结果都是null。

  虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。