目录介绍
-
0.关于四种引用
- 0.1 引用说明
- 0.2 关于Java下ref包和Android下ref包
-
1.强引用
- 1.0 关于强引用引用的场景
- 1.1 强引用介绍
- 1.2 强引用的特点
- 1.3 注意相互引用情况
-
2.软引用
- 2.0 关于SoftReference软引用
- 2.1 软引用应用场景
- 2.2 软引用的简单使用
- 2.3 软引用的特点
- 2.4 实际应用案例
- 2.5 注意避免软引用获取对象为null
-
3.弱引用
- 3.0 关于WeakReference弱引用
- 3.1 WeakReference:防止内存泄漏,要保证内存被虚拟机回收
- 3.1.1 先看一个handler小案例
- 3.1.2 为什么这样会造成内存泄漏
- 3.1.3 根本原因
- 3.2 弱引用解决办法
- 3.3 弱引用实际应用案例
-
4.虚引用
- 4.0 关于PhantomReference类虚引用
- 4.1 Android实际开发中没有用到过
-
5.四种引用其他介绍
- 5.1 弱引用和软引用区别
- 5.2 使用软引用或者弱引用防止内存泄漏
- 5.3 到底什么时候使用软引用,什么时候使用弱引用呢?
- 5.4 四种引用用一张表总结[摘自网络]
-
6.源码分析
- 6.1 首先看看如何通过弱引用加载图片
- 6.2 看看Reference的源代码
- 6.3 看看ReferenceQueue的enqueue函数
- 6.4 看看ReferenceQueue的enqueueLocked(Reference)函数
- 6.5 接着看看ReferenceQueue.isEnqueued()代码
- 6.6 那么enqueueLocked(Reference)函数中的Cleaner是做什么的
- 6.7 软引用SoftReference源码
- 6.8 弱引用WeakReference源码
- 6.9 虚引用PhantomReference源码
-
7.关于其他
- 7.1 关于参考案例
- 7.2 关于版本说明
- 7.3 关于我的博客
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong211/YCBlogs
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
特此说明
- 博客汇总链接:https://www.jianshu.com/p/53017c3fc75d
- 视频案例:https://github.com/yangchong211/YCVideoPlayer
- 音频案例:https://github.com/yangchong211/YCAudioPlayer
- recyclerView复杂封装库:https://github.com/yangchong211/YCRefreshView
- 状态栏工具类:https://github.com/yangchong211/YCStatusBar
- 状态切换管理器库:https://github.com/yangchong211/YCStateLayout
0.关于四种引用
0.1 引用说明
- java.lang.ref包中提供了几个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它可以和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对象的活动。
0.2 关于Java下ref包和Android下ref包
-
在Android下的ref包结构
-
在java下的ref包
1.强引用
1.0 关于强引用引用的场景
- 直接new出来的对象
- String str = new String("yc");
1.1 强引用介绍
- 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
- 通过引用,可以对堆中的对象进行操作。在某个函数中,当创建了一个对象,该对象被分配在堆中,通过这个对象的引用才能对这个对象进行操作。
1.2 强引用的特点
- 强引用可以直接访问目标对象。
- 强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
- 强引用可能导致内存泄露。
1.3 注意相互引用情况
2.软引用
2.0 关于SoftReference软引用
- SoftReference:软引用–>当虚拟机内存不足时,将会回收它指向的对象;需要获取对象时,可以调用get方法。
- 可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象,不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收。当堆的使用率临近阈值时,才会回收软引用的对象。
2.1 软引用应用场景
- 例如从网络上获取图片,然后将获取的图片显示的同时,通过软引用缓存起来。当下次再去网络上获取图片时,首先会检查要获取的图片缓存中是否存在,若存在,直接取出来,不需要再去网络上获取。
2.2 软引用的简单使用
- 用法如下
MyObject aRef = new MyObject();
SoftReference aSoftRef = new SoftReference(aRef);
MyObject anotherRef = (MyObject)aSoftRef.get();
2.3 软引用的特点
- 2.3.1 特点:
- 如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
- 2.3.2 代码如下
ReferenceQueue queue = new ReferenceQueue();
SoftReference ref = new SoftReference(aMyObject, queue);
- 2.3.3 如何回收:
- 那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
- 在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收。于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。
- 常用的方式为
SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
// 清除ref
}
2.4 实际应用案例
- 2.4.1 正常是用来处理图片这种占用内存大的情况
- 代码如下所示
View view = findViewById(R.id.button);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
Drawable drawable = new BitmapDrawable(bitmap);
SoftReference<Drawable> drawableSoftReference = new SoftReference<Drawable>(drawable);
if(drawableSoftReference != null) {
view.setBackground(drawableSoftReference.get());
}
- 2.4.2 这样使用软引用好处
- 通过软引用的get()方法,取得drawable对象实例的强引用,发现对象被未回收。在GC在内存充足的情况下,不会回收软引用对象。此时view的背景显示
- 实际情况中,我们会获取很多图片.然后可能给很多个view展示, 这种情况下很容易内存吃紧导致oom,内存吃紧,系统开始会GC。这次GC后,drawables.get()不再返回Drawable对象,而是返回null,这时屏幕上背景图不显示,说明在系统内存紧张的情况下,软引用被回收。
- 使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。
2.5 注意避免软引用获取对象为null
- 在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。
3.弱引用
3.0 关于WeakReference弱引用
- 3.0.1 WeakReference
- 弱引用–>随时可能会被垃圾回收器回收,不一定要等到虚拟机内存不足时才强制回收。要获取对象时,同样可以调用get方法。
- 3.0.2 特点
- 如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
- 弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
3.1 WeakReference:防止内存泄漏,要保证内存被虚拟机回收
-
3.1.1 先看一个handler小案例【千万不要忽视淡黄色警告】
3.1.2 为什么这样会造成内存泄漏
这种情况就是由于android的特殊机制造成的:当一个android主线程被创建的时候,同时会有一个Looper对象被创建,而这个Looper对象会实现一个MessageQueue(消息队列),当我们创建一个handler对象时,而handler的作用就是放入和取出消息从这个消息队列中,每当我们通过handler将一个msg放入消息队列时,这个msg就会持有一个handler对象的引用。因此当Activity被结束后,这个msg在被取出来之前,这msg会继续存活,但是这个msg持有handler的引用,而handler在Activity中创建,会持有Activity的引用,因而当Activity结束后,Activity对象并不能够被gc回收,因而出现内存泄漏。
3.1.3 根本原因
Activity在被结束之后,MessageQueue并不会随之被结束,如果这个消息队列中存在msg,则导致持有handler的引用,但是又由于Activity被结束了,msg无法被处理,从而导致永久持有handler对象,handler永久持有Activity对象,于是发生内存泄漏。但是为什么为static类型就会解决这个问题呢?因为在java中所有非静态的对象都会持有当前类的强引用,而静态对象则只会持有当前类的弱引用。声明为静态后,handler将会持有一个Activity的弱引用,而弱引用会很容易被gc回收,这样就能解决Activity结束后,gc却无法回收的情况。
3.2 弱引用解决办法
- 代码如下所示
private MyHandler handler = new MyHandler(this);
private static class MyHandler extends Handler{
WeakReference<FirstActivity> weakReference;
MyHandler(FirstActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
}
}
}
3.3 弱引用实际应用案例
- 具体案例可以参考我的代码:https://github.com/yangchong211/YCVideoPlayer
4.虚引用
4.0 关于PhantomReference类虚引用
- 虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,销毁这个对象,奖这个虚引用加入引用队列。
4.1 Android实际开发中没有用到过
- 貌似开发中没有接触过虚引用
5.四种引用其他介绍
5.1 弱引用和软引用区别
- 弱引用与软引用的根本区别在于:只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具有软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,通常不被回收。
5.2 使用软引用或者弱引用防止内存泄漏
- 在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。
- 软引用,弱引用都非常适合来保存那些可有可无的缓存数据。如果这样做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间。
5.3 到底什么时候使用软引用,什么时候使用弱引用呢?
- 个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。
- 还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。
- 另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。WeakHashMap使用ReferenceQueue实现的这种机制。
5.4 四种引用用一张表总结[摘自网络]
6.源码分析
6.1 首先看看如何通过弱引用加载图片
6.2 看看Reference的源代码
6.2.1 源码说明:
看到Reference除了带有对象引用referent的构造函数,还有一个带有ReferenceQueue参数的构造函数。那么这个ReferenceQueue用来做什么呢?
需要我们从enqueue这个函数来开始分析。当系统要回收Reference持有的对象引用referent的时候,Reference的enqueue函数会被调用,而在这个函数中调用了ReferenceQueue的enqueue函数。
那么我们来看看ReferenceQueue的enqueue函数做了什么?
6.2.2 看看这段源代码
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();
}
private final native T getReferent();
//清除此引用对象。调用此方法不会将对象加入队列
public void clear() {
this.referent = null;
}
//是否引用对象已进入队列,由程序或垃圾收集器。
//如果该引用对象在创建队列时没有注册,则该方法将始终返回
public boolean isEnqueued() {
return queue != null && queue.isEnqueued(this);
}
//添加引用对象到其注册的队列,如果他的方法是通过java代码调用
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;
}
}
6.3 看看ReferenceQueue的enqueue函数
- 6.3.1 源码说明
- 可以看到首先获取同步锁,然后调用了enqueueLocked(Reference)函数
-
6.3.2 看看这段代码
6.4 看看ReferenceQueue的enqueueLocked(Reference)函数
- 6.4.1 源码说明
- 通过 enqueueLocked函数可以看到ReferenceQueue维护了一个队列(链表结构),而enqueue这一系列函数就是将reference添加到这个队列(链表)中
-
6.4.2 看看这段代码
6.5 接着看看ReferenceQueue.isEnqueued()代码
- 6.5.1 让我们回到Reference源码中
- 可以看到除了enqueue这个函数还有一个isEnqueued函数,同样这个函数调用了ReferenceQueue的同名函数,源码如下:
boolean isEnqueued(Reference<? extends T> reference) {
synchronized (lock) {
return reference.queueNext != null && reference.queueNext != sQueueNextUnenqueued;
}
}
- 6.5.2 源码分析说明
- 可以看到先获取同步锁,然后判断该reference是否在队列(链表)中。由于enqueue和isEnqueue函数都要申请同步锁,所以这是线程安全的。
- 这里要注意“reference.queueNext != sQueueNextUnenqueued”用于判断该Reference是否是一个Cleaner类,在上面ReferenceQueue的enqueueLocked函数中我们可以看到如果一个Reference是一个Cleaner,则调用它的clean方法,同时并不加入链表,并且将其queueNext设置为sQueueNextUnequeued,这是一个空的虚引用
6.6 那么enqueueLocked(Reference)函数中的Cleaner是做什么的
- 在stackoverflow网站中找到这个解释
- sun.misc.Cleaner是JDK内部提供的用来释放非堆内存资源的API。JVM只会帮我们自动释放堆内存资源,但是它提供了回调机制,通过这个类能方便的释放系统的其他资源。
- 可以看到Cleaner是用于释放非堆内存的,所以做特殊处理。
- 通过enqueue和isEnqueue两个函数的分析,ReferenceQueue队列维护了那些被回收对象referent的Reference的引用,这样通过isEnqueue就可以判断对象referent是否已经被回收,用于一些情况的处理。
6.7 软引用SoftReference源码
6.7.1 关于这段源码分析
-
可以看到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时,通过上面的条件去判断软应用是否可以回收并进行回收,即我们通常说的内存不足时被回收。
6.7.2 源码如下所示
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;
}
}
6.8 弱引用WeakReference源码
- 6.8.1 源码分析说明
- 没有其他代码,GC时被回收掉。
- 6.8.2 源码如下所示
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
6.9 虚引用PhantomReference源码
6.9.1 源码分析说明
可以看到get函数返回null,正如前面说得虚引用无法获取对象引用。(注意网上有些文章说虚引用不持有对象的引用,这是有误的,通过构造函数可以看到虚引用是持有对象引用的,但是无法获取该引用
同时可以看到虚引用只有一个构造函数,所以必须传入ReferenceQueue对象。
前面提到虚引用的作用是判断对象是否被回收,这个功能正是通过ReferenceQueue实现的。
这里注意:不仅仅是虚引用可以判断回收,弱引用和软引用同样实现了带有ReferenceQueue的构造函数,如果创建时传入了一个ReferenceQueue对象,同样也可以判断。
6.9.2 源码如下所示
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
7.关于其他
7.1 关于参考案例
- http://blog.csdn.net/qq_20280683/article/details/77897876
- http://blog.csdn.net/to_be_designer/article/details/72673421
7.2 关于版本说明
- v1.0 16年6月18日
- v1.1 17年11月18日,简单的分析了源码
- v1.2 18年重新整理了笔记
7.3 关于我的博客
- 我的个人站点:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV