详细分析Android中的引用机制Reference(WeakReference、SoftReference、PhantomReference)
目录
1、前言
2、四种引用
3、java.lang.ref
4、Reference
5、ReferenceQueue.enqueue(Reference)
6、ReferenceQueue.isEnqueued()
7、SoftReference
8、WeakReference
9、PhantomReference
10、总结
1、前言
在java中,我们知道一般情况下当一个对象被其他对象引用时,该对象则不会被回收。但是有时我们虽然需要使用该对象,但又希望不影响回收。
比如在Activity中以内部类的方式创建了一个Handler,这个Handler就会隐式的持有一个activity的引用,当这个Handler被一个耗时线程所引用。这时如果关闭这个Activity,由于被引用该Activity及它所持有的引用占用的内存将不能被销毁,这样就导致了内存泄漏。
这时候我们可以使用“弱引用”来解决问题。
2、四种引用
- 强引用:就是代码中普遍存在的引用,一般情况下只要存在强引用就不会被回收。(这里要注意相互引用的情况,我们会在另外一篇来说)
- 软引用(SoftReference):只有软引用关联的对象,当内存不足时会被回收(细节下面会说)
- 弱引用(WeakReference):在广义上除了强引用都是弱引用,这里我们说的是狭义上的弱引用。弱引用比软引用还要更容易被回收,当GC过程中发现只有弱引用的对象时,不论内存是否足够都会被回收。
- 虚引用(PhantomReference):虚引用对对象的生存不产生任何影响,而且通过虚引用无法获取对象实例。虚引用的作用是我们可以通过它来判断对象是否已经被回收,细节我们下面再聊。
3、java.lang.ref
可以看到jdk下多了Finalizer和FinalReference这两个类。其中FinalReference是Reference的子类,而Finalizer则是FinalReference的子类。
本章我们讨论Android系统下的引用,java引用我们以后另开一章来讨论。
其中SoftReference、WeakReference、PhantomReference都是Reference的子类,而ReferenceQueue则是Reference的一个重要的组成部分。
下面我们来看看这几个类的源码。
4、Reference
public abstract class Reference<T> {private static boolean disableIntrinsic = false;private static boolean slowPathEnabled = false;volatile T referent; /* Treated specially by GC */final ReferenceQueue<? super T> queue;Reference queueNext;Reference<?> pendingNext;public T get() {return getReferent();}@FastNativeprivate final native T getReferent();public void clear() {clearReferent();}@FastNativenative void clearReferent();public boolean isEnqueued() {return queue != null && queue.isEnqueued(this);}public boolean enqueue() {return queue != null && queue.enqueue(this);}Reference(T referent) {this(referent, null);}Reference(T referent, ReferenceQueue<? super T> queue) {this.referent = referent;this.queue = queue;}
}
代码只有二三十行,我们看到Reference除了带有对象引用referent的构造函数,还有一个带有ReferenceQueue参数的构造函数。那么这个ReferenceQueue用来做什么呢?需要我们从enqueue这个函数来开始分析。当系统要回收Reference持有的对象引用referent的时候,Reference的enqueue函数会被调用,而在这个函数中调用了ReferenceQueue的enqueue函数。那么我们来看看ReferenceQueue的enqueue函数做了什么?
5、ReferenceQueue.enqueue(Reference)
boolean enqueue(Reference<? extends T> reference) {synchronized (lock) {if (enqueueLocked(reference)) {lock.notifyAll();return true;}return false;}
}
可以看到首先获取同步锁,然后调用了enqueueLocked(Reference)函数,该函数源码如下:
private boolean enqueueLocked(Reference<? extends T> r) {// Verify the reference has not already been enqueued.if (r.queueNext != null) {return false;}if (r instanceof Cleaner) {Cleaner cl = (sun.misc.Cleaner) r;cl.clean();r.queueNext = sQueueNextUnenqueued;return true;}if (tail == null) {head = r;} else {tail.queueNext = r;}tail = r;tail.queueNext = r;return true;
}
通过 enqueueLocked函数可以看到ReferenceQueue维护了一个队列(链表结构),而enqueue这一系列函数就是将reference添加到这个队列(链表)中。
6、ReferenceQueue.isEnqueued()
boolean isEnqueued(Reference<? extends T> reference) {synchronized (lock) {return reference.queueNext != null && reference.queueNext != sQueueNextUnenqueued;}
}
可以看到先获取同步锁,然后判断该reference是否在队列(链表)中。由于enqueue和isEnqueue函数都要申请同步锁,所以这是线程安全的。
这里要注意“reference.queueNext != sQueueNextUnenqueued”用于判断该Reference是否是一个Cleaner类,在上面ReferenceQueue的enqueueLocked函数中我们可以看到如果一个Reference是一个Cleaner,则调用它的clean方法,同时并不加入链表,并且将其queueNext设置为sQueueNextUnequeued,这是一个空的虚引用,如下:
private static final Reference sQueueNextUnenqueued = new PhantomReference(null, null);
那么什么是Cleaner?引用一段描述
sun.misc.Cleaner是JDK内部提供的用来释放非堆内存资源的API。JVM只会帮我们自动释放堆内存资源,但是它提供了回调机制,通过这个类能方便的释放系统的其他资源。
可以看到Cleaner是用于释放非堆内存的,所以做特殊处理。
通过enqueue和isEnqueue两个函数的分析,ReferenceQueue队列维护了那些被回收对象referent的Reference的引用,这样通过isEnqueue就可以判断对象referent是否已经被回收,用于一些情况的处理。
7、SoftReference
public class SoftReference<T> extends Reference<T> {static private long clock;private long timestamp;public SoftReference(T referent) {super(referent);this.timestamp = clock;}public SoftReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);this.timestamp = clock;}public T get() {T o = super.get();if (o != null && this.timestamp != clock)this.timestamp = clock;return o;}}
可以看到SoftReference有一个类变量clock和一个变量timestamp,这两个参数对于SoftReference至关重要。
- clock:记录了上一次GC的时间。这个变量由GC(garbage collector)来改变。
- timestamp:记录对象被访问(get函数)时最近一次GC的时间。
那么这两个参数有什么用?
我们知道软引用是当内存不足时可以回收的。但是这只是大致情况,实际上软应用的回收有一个条件:
clock -timestamp <= free_heap * ms_per_mb
- free_heap是JVM Heap的空闲大小,单位是MB
- ms_per_mb单位是毫秒,是每MB空闲允许保留软引用的时间。Sun JVM可以通过参数-XX:SoftRefLRUPolicyMSPerMB进行设置
举个栗子:
目前有3MB的空闲,ms_per_mb为1000,这时如果clock和timestamp分别为5000和2000,那么5000 - 2000 <= 3 * 1000条件成立,则该次GC不对该软引用进行回收。
所以每次GC时,通过上面的条件去判断软应用是否可以回收并进行回收,即我们通常说的内存不足时被回收。
8、WeakReference
public class WeakReference<T> extends Reference<T> {public WeakReference(T referent) {super(referent);}public WeakReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}}
9、PhantomReference
public class PhantomReference<T> extends Reference<T> {public T get() {return null;}public PhantomReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}}
可以看到get函数返回null,正如前面说得虚引用无法获取对象引用。(注意网上有些文章说虚引用不持有对象的引用,这是有误的,通过构造函数可以看到虚引用是持有对象引用的,但是无法获取该引用)
同时可以看到虚引用只有一个构造函数,所以必须传入ReferenceQueue对象。
前面提到虚引用的作用是判断对象是否被回收,这个功能正是通过ReferenceQueue实现的(文章第5、6点讲的)。
这里注意:不仅仅是虚引用可以判断回收,弱引用和软引用同样实现了带有ReferenceQueue的构造函数,如果创建时传入了一个ReferenceQueue对象,同样也可以判断。
10、总结
详细分析Android中的引用机制Reference(WeakReference、SoftReference、PhantomReference)相关推荐
- 从源码角度分析Android中的Binder机制的前因后果
为什么在Android中使用binder通信机制? 众所周知linux中的进程通信有很多种方式,比如说管道.消息队列.socket机制等.socket我们再熟悉不过了,然而其作为一款通用的接口,通信开 ...
- 重温Android中的消息机制
引入: 提到Android中的消息机制,大家应该都不陌生,我们在开发中不可避免的要和它打交道.从我们开发的角度来看,Handler是Android消息机制的上层接口.我们在平时的开发中只需要和Hand ...
- Android中Alarm的机制
本次给大家分析的是Android中Alarm的机制所用源码为最新的Android4.4.4.首先简单介绍如何使用Alarm并给出其工作原理,接着分析Alarm和Timer以及Handler在完成定时任 ...
- android系统的alarm机制,Android中Alarm的机制
本次给大家分析的是Android中Alarm的机制所用源码为最新的Android4.4.4.首先简单介绍如何使用Alarm并给出其工作原理,接着分析Alarm和Timer以及Handler在完成定时任 ...
- 如何调试分析Android中发生的tombstone
2019独角兽企业重金招聘Python工程师标准>>> 如何调试分析Android中发生的tombstone Android中较容易出现以下三类问题:Force close / AN ...
- 浅析Android中的消息机制
在分析Android消息机制之前,我们先来看一段代码: [java] view plaincopy public class MainActivity extends Activity impleme ...
- 探索Android中的Parcel机制(上)
一.先从Serialize说起 我们都知道JAVA中的Serialize机制,译成串行化.序列化--,其作用是能将数据对象存入字节流其中,在须要时又一次生成对象.主要应用是利用外部存储设备保存对象状态 ...
- 探索Android中的Parcel机制(上) .
一.先从Serialize说起 我们都知道JAVA中的Serialize机制,译成串行化.序列化--,其作用是能将数据对象存入字节流当中,在需要时重新生成对象.主要应用是利用外部存储设备保存对象状态, ...
- android classloader异常,Android中ClassLoader类加载机制
Android中apk的构建过程 构建apk 如图 所示,典型 Android 应用模块的构建流程通常依循下列步骤: 编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中 ...
最新文章
- 【渝粤题库】陕西师范大学189101 消费者行为学Ⅰ 作业(高起专)
- 160 - 30 cracking4all.1
- 红橙Darren视频笔记setContentView源码分析 xml加载的过程
- Windows Mobile 中怎样获得当前应用程序所在路径
- Leetcode每日一题:100.same-tree(相同的树)
- 《Javascript入门学习全集》 Javascript学习第一季(7)
- Spark Conf配置用法
- qq视频转码失败怎么办_迅捷视频转换器转换失败的解决方法
- 计算机上未检测到u盾,u盾检测不到-电脑上检测不到我的U盾怎么办? 爱问知识人...
- SpringBoot大学毕业生就业信息管理系统
- 菜鸟日志:ADL(C++参数依赖查找)、
- css3制作翘边阴影
- 日常开发中,你需要掌握的git使用报错解决
- 如何看待何恺明最新一作论文Masked Autoencoders?
- 第13期微生物组-宏基因组分析(线上/线下同时开课,2021.11)
- 如何在Apple Watch上获取行车路线
- 烂泥:学习ubuntu远程桌面(一):配置远程桌面
- 单样本t检验中指标分析
- oracle实现列转行
- 大厂HR:我让你测试一个(电梯、杯子、笔、桌子、洗衣机),你会怎么测试它?