不仅仅是Andorid,在编写任何JAVA工作项目中我们都要考虑到内存泄漏和OOM的问题。在JDK的java.lang.ref包中提供了四种引用方式:强引用(StrongReference),软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)。通过使用这些不同的引用方式,可以在项目中有效加强对内存的管理。
为什么要使用这些引用方式?
四种引用方式的出现是为了解决在编程中出现的内存泄漏与内存溢出问题的。那么在什么情况下我们会遇到内存问题?我们又为什么要使用这些不同的引用方式来管理内存?是否有更好的方法来达到同样的目的呢?使用引用的情况并不局限于以下几项,而且下面几项问题的解决方法也并不仅限于使用不同类型的引用。这仅是我们日常较为常见的几种情况,更多的时候四种引用的使用在jdk源代码中更加常见。
1.移动端内存小,加载大量数据
如果你有过Andorid的开发经验,你一定会了解在Andorid平台上加载大量的图片是必须进行优化的,否则可能导致性能极差。对加载图片的处理优化方法非常多,使用软引用而不是强应用来管理内存从而节省时间提升性能是一种可行的办法。
2.回调函数与监听器导致的内存泄漏
回调函数是一种在两个对象之间进行信息传递的方法,监听器也是类似功能的一种技术。监听器在使用的时候被注册,在试用结束之后你是否确认过这个对象能够被正确的回收呢?用其他类型的引用替换强引用不失为一种方法。
3.静态集合类导致内存占用
很大程度上是WeakHashMap的出现才让我们真正开始关注WeakReference。WeakHashMap解决了HashMap存在的回收问题,让那些在HashMap中已经没有活力的数据能够得以回收。
4.匿名内部类持有外部变量导致内存泄漏
匿名内部类在持有外围变量的时候需要声明为final的原因就是因为为了防止在方法执行时变量值变化导致的不一致情况。然而在方法被执行时,匿名内部类的生命周期很可能已经结束,为了保证这个传入方法中的final变量能够被回收,使用弱引用替换强引用是可以的。
5.通过引用队列ReferenceQueue存放已被回收的对象
有的时候需要进行特殊的工作,比如在一个对象被回收清理时进行操作,亦或者是在对象被回收之后需要进行操作处理,这是可以使用虚引用和引用队列来满足需求。实际上,这种情况并不是很多见。
强引用 StrongReference
强引用是最为常见且平常的引用方式。在代码中new而得到的对象的引用就是强引用,这种引用会一直维持下去,gc回收并不会回收拥有强引用的对象。在引用被赋予null后,gc才回去回收堆中没有引用连接的对象。
package ReferenceEx;
public class ReferenceEx {
public static void main(String[] args) {
// TODO Auto-generated method stub
Object objStrong = new Object();
// 创建了强引用objStrong,objStrong指向的对象不会被gc回收。
System.gc();
// 主动调用gc回收。
System.out.println(objStrong.toString());
// 输出:java.lang.Object@15db9742,表示objStrong没有被回收。
objStrong = null;
System.gc();
// 刚才new创建的Object对象将会被回收。
}
}
软引用 SoftReference
软引用的强度仅次于强引用,在一般情况下调用gc进行回收是不会将弱引用的对象回收的。如果一个对象只具有软引用,当内存不够用时,gc回收会将弱引用的对象回收以节省内存。除此之外软引用和强引用差别不大。这种特性也导致了软引用的很多用处,比如在网页被刷新后,我们可以选择将之前浏览的网页通过软引用放入缓存中,当网页被退回时可以直接加载缓存中的原网页,而当内存不够用的时候软引用会被自动清理,也不需要我们再操心。所以,软引用可用来实现内存敏感的高速缓存。
package ReferenceEx;
import java.lang.ref.SoftReference;
public class ReferenceEx {
public static void main(String[] args) {
// TODO Auto-generated method stub
Object sr1;
SoftReference<Object> sr = new SoftReference<>(new Object());
// 在程序运行的过程中,如果JVM内存不足
// 因为new Object()只具有软引用sr,所以Object对象将会被回收
// 此时 sr=null;
if (sr != null)
sr1 = sr;
// 当我要使用sr时先进行判断,如果sr不为null
// 说明sr没有被回收,我们可以建立新的引用,从而操作Object对象
else {
sr = new SoftReference<>(new Object());
sr1 = sr;
// 如果sr是null说明已经内存不够被回收
// 此时我们需要重新创建Object对象
}
}
}
弱引用 WeakReference
弱引用的强度更低,低于软引用,弱引用的对象拥有更短暂的生命周期。当gc进行回收时,不管当前内存空间足够与否,都会将只有弱引用的对象全部回收。但是问题在于gc线程优先级并没有想象中那么高,所以我们很难具体判断只具有弱引用的对象什么时候被回收。也正是如此,通常弱引用和ReferenceQueue一起使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
package ReferenceEx;
import java.lang.ref.WeakReference;
public class ReferenceEx {
public static void main(String[] args) {
// TODO Auto-generated method stub
WeakReference<Object> wr = new WeakReference<>(new Object());
// 创建弱引用wr,表示一个object对象。
System.out.println(wr.get());
// 输出java.lang.Object@15db9742,表示该弱引用尚未被回收。
System.gc();
// 此处直接调用gc方法,快速回收所有弱引用。
System.out.println(wr.get());
// 输出null,弱引用已被回收。
}
}
虚引用 PhantomReference
虚引用是强度最弱的一种引用,这种引用弱到它的 get() 方法永远返回 null。那么虚引用有什么作用呢? 虚引用主要用来跟踪对象被gc回收的活动。虚引用必须使用ReferenceQueue。当gc准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的ReferenceQueue中。这样我们知道一个引用什么时候被清理,也就可以在被清理的时候做一些想要做的工作。虚引用可以满足一些特殊的需求。
package ReferenceEx;
import java.lang.ref.PhantomReference;
public class ReferenceEx {
public static void main(String[] args) {
// TODO Auto-generated method stub
PhantomReference<Object> pr = new PhantomReference<>(new Object(), null);
// 创建虚引用pr,表示一个object对象。
System.out.println(pr.get());
// 输出null,任何情况下虚引用都会显示null。
System.gc();
// 此处直接调用gc方法,快速回收所有可回收的引用。
System.out.println(pr.get());
// 输出null,任何情况下虚引用都会显示null。
}
}
引用队列 ReferenceQueue
如果不研究ReferenceQueue,那么虚引用将没有任何作用、弱引用的效果将大打折扣。在软引用、弱引用和虚引用的构造器中我们都可以添加ReferenceQueue参数,将他们与一个引用队列关联起来。那么在程序运行的过程中,ReferenceQueue在检测到适当更改后,gc会将已注册回收的引用对象添加到该队列中。
方法名 | 作用 |
---|---|
poll() | 轮询此队列,查看是否存在可用的引用对象。 |
Reference remove() | 移除此队列中的下一个引用对象,阻塞到某个对象变得可用为止。 |
Reference remove(long timeout) | 移除此队列下一个引用对象,阻塞到有一个对象变得可用或者给定的超时期满了为止。 |
可以看出ReferenceQueue中的方法操作所有的被添加进入队列的引用对象,这便于我们做很多特殊的工作。
四种引用效果与作用对比
引用类型 | 被gc回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | JVM退出后强引用结束 | |
软引用 | 内存不足 | 对象缓存 | 内存不足时软引用结束 |
弱引用 | 垃圾回收 | 对象缓存,与引用队列配合 | 垃圾回收后弱引用结束 |
虚引用 | 与引用队列配合使用 |
表格中有三处空:
1. 强引用是作为程序中最普遍的声明引用,并没有特殊的用途。
2. 虚引用被gc回收的时间很难说,只要是调用虚引用的get方法肯定会返回null值。理论上,一般在调用get方法时虚引用会被回收,但这并不意味着gc对整个程序进行过垃圾回收。
3. 虚引用的生存时间也很难说,理由同上,只要是调用虚引用的get方法肯定会返回null值。我们只需要知道虚引用与引用队列配合的用处,并不需要深究虚引用具体的回收时间,生存时间等概念。