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

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

复制代码

 1 import java.lang.ref.SoftReference;
 2 
 3 public class ReferenceTest {
 4 
 5     public static void main(String[] args) {
 6 
 7         A a = new A();
 8         
 9         SoftReference<A> srA = new SoftReference<A>(a);
10 
11         a = null;
12 
13         if (srA.get() == null) {
14             System.out.println("a对象进入垃圾回收流程");
15         } else {
16             System.out.println("a对象尚未被回收" + srA.get());
17         }
18 
19         // 垃圾回收
20         System.gc();
21 
22         if (srA.get() == null) {
23             System.out.println("a对象进入垃圾回收流程");
24         } else {
25             System.out.println("a对象尚未被回收" + srA.get());
26         }
27 
28     }
29 }
30 
31 class A {
32 
33 }

复制代码

##输出结果为:

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

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

复制代码

 1 import java.lang.ref.WeakReference;
 2 
 3 public class ReferenceTest {
 4 
 5     public static void main(String[] args) {
 6 
 7         A a = new A();
 8 
 9         WeakReference<A> wrA = new WeakReference<A>(a);
10 
11         a = null;
12 
13         if (wrA.get() == null) {
14             System.out.println("a对象进入垃圾回收流程");
15         } else {
16             System.out.println("a对象尚未被回收" + wrA.get());
17         }
18 
19         // 垃圾回收
20         System.gc();
21 
22         if (wrA.get() == null) {
23             System.out.println("a对象进入垃圾回收流程");
24         } else {
25             System.out.println("a对象尚未被回收" + wrA.get());
26         }
27 
28     }
29 
30 }
31 
32 class A {
33 
34 }

复制代码

##输出结果为:

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

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

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

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

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

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

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

复制代码

 1 public class MainActivity extends Activity {
 2 
 3     //...
 4     private int page;
 5     private Handler handler = new Handler() {
 6 
 7         @Override
 8         public void handleMessage(Message msg) {
 9             if (msg.what == 1) {
10 
11                 //...
12 
13                 page++;
14             } else {
15 
16                 //...
17 
18             }
19 
20         };
21     };
22 
23     @Override
24     protected void onCreate(Bundle savedInstanceState) {
25         super.onCreate(savedInstanceState);
26         setContentView(R.layout.activity_main);
27 
28         //...
29 
30         new Thread(new Runnable() {
31             @Override
32             public void run() {
33                 //.. 
34                 Message msg = Message.obtain();
35                 msg.what = 1;
36                 //msg.obj = xx;
37                 handler.sendMessage(msg);
38             }
39         }).start();
40 
41         //...
42 
43     }
44 
45 }

复制代码

在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官方给出的建议写法为:

复制代码

 1 public class MainActivity extends Activity {
 2 
 3     //...
 4     private int page;
 5     private MyHandler mMyHandler = new MyHandler(this);
 6 
 7     private static class MyHandler extends Handler {
 8 
 9         private WeakReference<MainActivity> wrActivity;
10 
11         public MyHandler(MainActivity activity) {
12             this.wrActivity = new WeakReference<MainActivity>(activity);
13         }
14 
15         @Override
16         public void handleMessage(Message msg) {
17             if (wrActivity.get() == null) {
18                 return;
19             }
20             MainActivity mActivity = wrActivity.get();
21             if (msg.what == 1) {
22 
23                 //...
24                 mActivity.page++;
25 
26             } else {
27 
28                 //...
29 
30             }
31         }
32 
33     }
34 
35     @Override
36     protected void onCreate(Bundle savedInstanceState) {
37         super.onCreate(savedInstanceState);
38         setContentView(R.layout.activity_main);
39 
40         //...
41 
42         new Thread(new Runnable() {
43             @Override
44             public void run() {
45                 //.. 
46                 Message msg = Message.obtain();
47                 msg.what = 1;
48                 //msg.obj = xx;
49                 mMyHandler.sendMessage(msg);
50             }
51         }).start();
52 
53         //...
54 
55     }
56 
57 }

复制代码

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

看下面的一个例子:

复制代码

 1 public class ReferenceTest {
 2 
 3     public static void main(String[] args) {
 4 
 5         A a = new A();
 6 
 7         WeakReference<A> wrA = new WeakReference<A>(a);
 8 
 9         a = null;
10 
11         if (wrA.get() == null) {
12             System.out.println("a对象进入垃圾回收流程");
13         } else {
14             System.out.println("a对象尚未被回收" + wrA.get());
15         }
16 
17         // 垃圾回收
18         System.gc();
19 
20         if (wrA.get() == null) {
21             System.out.println("a对象进入垃圾回收流程");
22         } else {
23             System.out.println("a对象尚未被回收" + wrA.get());
24         }
25 
26     }
27 }
28 
29 class A {
30 
31     @Override
32     protected void finalize() throws Throwable {
33         super.finalize();
34         System.out.println("in A finalize");
35     }
36 
37 }

复制代码

##输出结果为:

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

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

复制代码

 1 public class ReferenceTest {
 2 
 3     public static void main(String[] args) {
 4 
 5         A a = new A();
 6 
 7         ReferenceQueue<A> rq = new ReferenceQueue<A>();
 8         WeakReference<A> wrA = new WeakReference<A>(a, rq);
 9 
10         a = null;
11 
12         if (wrA.get() == null) {
13             System.out.println("a对象进入垃圾回收流程");
14         } else {
15             System.out.println("a对象尚未被回收" + wrA.get());
16         }
17 
18         System.out.println("rq item:" + rq.poll());
19 
20         // 垃圾回收
21         System.gc();
22 
23         if (wrA.get() == null) {
24             System.out.println("a对象进入垃圾回收流程");
25         } else {
26             System.out.println("a对象尚未被回收" + wrA.get());
27         }
28 
29         /*
30         try {
31             Thread.sleep(1000);
32         } catch (InterruptedException e) {
33             e.printStackTrace();
34         }
35         */
36 
37         System.out.println("rq item:" + rq.poll());
38 
39     }
40 }
41 
42 class A {
43 
44     @Override
45     protected void finalize() throws Throwable {
46         super.finalize();
47         System.out.println("in A finalize");
48     }
49 
50 }

复制代码

##输出结果为:

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

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

复制代码

 1 public class ReferenceTest {
 2 
 3     public static void main(String[] args) {
 4 
 5         A a = new A();
 6 
 7         ReferenceQueue<A> rq = new ReferenceQueue<A>();
 8         WeakReference<A> wrA = new WeakReference<A>(a, rq);
 9 
10         a = null;
11 
12         if (wrA.get() == null) {
13             System.out.println("a对象进入垃圾回收流程");
14         } else {
15             System.out.println("a对象尚未被回收" + wrA.get());
16         }
17 
18         System.out.println("rq item:" + rq.poll());
19 
20         // 垃圾回收
21         System.gc();
22 
23         if (wrA.get() == null) {
24             System.out.println("a对象进入垃圾回收流程");
25         } else {
26             System.out.println("a对象尚未被回收" + wrA.get());
27         }
28 
29         try {
30             Thread.sleep(1);
31         } catch (InterruptedException e) {
32             e.printStackTrace();
33         }
34 
35         System.out.println("rq item:" + rq.poll());
36 
37     }
38 }
39 
40 class A {
41 
42     @Override
43     protected void finalize() throws Throwable {
44         super.finalize();
45         System.out.println("in A finalize");
46     }
47 
48 }

复制代码

##输出结果为:

1 a对象尚未被回收A@6276e1db
2 rq item:null
3 a对象进入垃圾回收流程
4 in A finalize
5 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。

复制代码

 1 public class ReferenceTest {
 2 
 3     public static void main(String[] args) {
 4 
 5         A a = new A();
 6 
 7         ReferenceQueue<A> rq = new ReferenceQueue<A>();
 8         PhantomReference<A> prA = new PhantomReference<A>(a, rq);
 9 
10         System.out.println("prA.get():" + prA.get());
11         
12         a = null;
13         
14         System.gc();
15         
16         try {
17             Thread.sleep(1);
18         } catch (InterruptedException e) {
19             e.printStackTrace();
20         }
21 
22         System.out.println("rq item:" + rq.poll());
23 
24     }
25 }
26 
27 class A {
28 
29 }

复制代码

##输出结果为:

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

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

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

----------------------------------------------------------------------------------------------------------------------------

 

在java中,没用指针,对象的操作全部凭借引用来与之产生关联,通过操作相关联的引用,来达到操作所需对象的目的,所以,可以言简意赅的理解为,引用,是用来操作对象的。
例如:

User u1 = new User();

    1
    2

在当前的这句语句中,我们创建了一个对象User,然后这个对象会被分配到堆内存中的一个位置进行存储;而局部变量 u1被存储在栈内存中。

这时,我们通过一个引用名 为u1的引用对这个User进行相关联,通过u1我们就可以操作到这个User对象。

假如上面的那行代码是在一个方法体内执行的,而这时我们再写一行这样的代码

User u2 = u1;

    1

那么这时,u1持有可以操作的对象User,同时也会被u2持有。u1、u2同时关联同一个对象。
而在内存中,是这样的
image

,一个对象,是可以被多个引用持有的,而另外值得注意的一点是,并且可以是不同的引用。 这一段话为什么要字体加粗呢,因为这对于后面各种引用回收的理解,有着重要作用。
强引用(StrongReference)

四种引用中,强引用是我们使用的最多的一种引用对象,对强应用的使用,在编码过程中也是无处不在,例如创建一个成员变量,new 出一个对象等等……;

强引用可以直接访问目标对象,强引用所关联的对象,在任何时候都不会被内存回收,JVM宁肯抛出OOM异常,也不会对其进行回收,所以,在通常的内存泄漏中,大多都有强引用的身影。
软引用(SoftReference)

软引用是除了硬引用之外最强的一种引用,软引用和硬引用的不同点在于,软引用是可被回收的;

其回收机制是:当内存充足的时候,在GC时,不会去回收当前的软引用,当内存临近阈值或不足的时候,在GC时,发现某一对象的引用只具有软引用当前软引用就会被回收。

当一对象除了具有软引用还具有硬引用,GC时,会被回收吗?答案是肯定不会,只会回收只具有软引用的对象
另外值得说的一点是,以前缓存图片都会选择使用软引用,虽然它当内存不足时才回收的特点很符合作为缓存,但缺点也很明显

    关于这部分缓存被回收时,并没有一套机制去进行衡量,例如先回收离上次使用间隔时长最长的,而是随GC随机回收;

    当手机内存使用比较高的时候,那么在理论上来说,GC的频率也就会更高,需要不断的释放掉相应的内存来腾出空间,而在这个时候,也将意味着软引用,也将会被高频率清理,在这个过程中起到缓存的实质性质效果很低,软应用刚创建好不一会就被清理,不一会就被清理,站在性能优化的角度来说,效果并不明显,并且在被回收后还是需要不断重新创建,这也是消耗性能的。

所以关于图片的缓存,谷歌后来推出了LruCache,底层是LinkHashMap解决了上述的第一点问题,而可以设定存储大小则是很好的解决了第二点问题。
弱引用(WeakReference)

弱引用是比软引用和硬引用更弱的一种引用,在GC时,不论内存是否充足,发现某一对象的引用只具有弱引用当前弱引用就会被回收。

当一对象除了具有弱引用还具有硬引用,GC时,会被回收吗?答案是肯定不会,只会回收只具有弱引用的对象

在很多时候,弱引用会被组合起来一起使用,例如其中一个使用场景:Handler的匿名内部类的实现中,可能会导致内存泄漏,使用静态内部类解决持有外部引用问题,将需要的外部引用使用弱引用,而像在这部分的实际运用场景下,弱引用的使用是更符合规范,但不一定有效呢,比如弱引用持有的对象,这个对象还有硬引用,在GC时,这不符合回收规则,这个对象就不会被回收。
虚引用(PhantomReference)

虚引用不能保证其保存对象生命周期,其保存对象若只有虚引用,则其有效期完全随机于GC的回收,在任何一个不确定的时间内,都可能会被回收;而虚引用与其他几者的引用不同在于,在使用PhantomReference,必须要和Reference联合使用。
总结与注意事项

前面对各种引用进行了详细的解说,然后关于这几种引用,在使用过程中,有一个细节还是非常值得注意的,软引用,弱引用,虚引用,在创建引用对象的时候,除了传入引用对象,通常还有一个构造函数,多了一个ReferenceQueue

        ReferenceQueue queue = new ReferenceQueue();
        WeakReference weakReference = new WeakReference(this,queue);
        SoftReference softReference = new SoftReference(this,queue);
        PhantomReference phantomReference = new PhantomReference(this,queue);

    1
    2
    3
    4

那么这个ReferenceQueue有什么用呢?

引用对象本身,也是一个强引用,其除了具有保存一个对象本身特有的引用属性之外,引用对象本身也具有java对象的一般性,那么在其本身保存的对象被回收之后,引用对象本身也就没有了实用性质,需要一个适当的清理机制,来清理这些对象,避免大量这些引用对象而带来的内存泄漏;这时候,就可以用到ReferenceQueue。

当引用(SoftReference/WeakReference/PhantomReference)中保存的的对象,被GC回收时,引用本身的这个对象会被加入到ReferenceQueue中,那么,也就是说,ReferenceQueue中保存的对象是Reference,并且是失去了其保存的对象的Reference。这个时候我们可以通过调用ReferenceQueue中提供的poll()这个API来获取队列中的对象,当队列中不存在对象的时候,返回的会是null,当存在或存在多个的时候,都是返回最前面的一个Reference对象,这个时候我们就需要将这个对象进行清除,让相应的内存可以被释放掉。

Reference ref = null;
        while ((ref =  queue.poll()) != null) {
            // 清除ref
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值