内存回收
每一个 Java 程序中的对象都会占用一定的计算机资源,最常见的,如:每个对象都会在堆空间上申请一定的内存空间。但是除了内存之外,对象还会占用其它资源,如文件句柄,端口,socket 等等。当你创建一个对象的时候,必须保证它在销毁的时候会释放它占用的资源。否则程序将会在 OOM 中结束它的使命。
在 Java 中数据内存分配发生在栈和堆中,对于栈中的变量(引用类型,基本类型)会在方法的退出时自动释放,对于 new 出来对象分配到堆中,不需要程序员来管理内存的分配和释放,Java 有自动进行内存管理的神器——垃圾回收器,垃圾回收器会自动回收那些不再使用的对象。那如何判断对象不再使用呢?就是不在被引用的对象。
引用类型
最早的 JDK 中只存在一个引用类型,这样就对于垃圾收集器来说所有的引用对象回收都是平等的。这样对于开发人员来说无法把控对象回收时机,所有对象回收都交给了垃圾收集器。
为了更灵活的控制对象的生命周期,在 JDK1.2 之后,引用被划分为强引用、软引用、弱引用、虚引用四种类型,每种类型有不同的生命周期,它们不同的地方就在于垃圾回收器对待它们会使用不同的处理方式。这 4 种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
1、强引用 StrongReference
就是我们最普遍使用的方式,如
Object obj = new Object();
这样对于我们,就是创建一个强引用,只要引用存在,jvm 就不会回收引用对应的空间,哪怕是报出内存空间不足的异常,当我们确定不需要使用 obj 对象时,可以将 obj 置为 null,这样 jvm 就认为它将不再使用,可以回收,但具体回收的时机还要看 gc 策略。
2、软引用 SoftReference
内存空间不足时,进行垃圾回收,如果内存空间足时,不会发生回收。软引用一般和引用队列结合使用,当对象回收时,会加入到关联的引用队列中。
应用场景,软引用可以很好的用来实现缓存,当 JVM 需要内存时,垃圾回收器就会回收这些只有被软引用指向的对象。
public class SoftReferenceTest {
public static void main(String[] args) {
// 原始强引用
String welcome = "hello world";
ReferenceQueue<String> refQueue = new ReferenceQueue<String>();
SoftReference<String> welcomeRef = new SoftReference<>(welcome,refQueue);
// 置为空,这时可以垃圾回收
welcome = null;
// 这时不一定回收,只有空间不足时,才会回收
System.gc();
// 这时可能会引用到,也可能引用不到,就看是否空间不足,是否发生了回收
String welcomeValue = welcomeRef.get();
// 如果垃圾回收了,则值不为空,如果还没有回收,则为空
Reference<? extends String> valueRef = refQueue.poll();
}
}
3、弱引用 WeakReference
与软引用相似,区别就是发生垃圾回收时,就会将弱引用对象进行回收,而不用考虑空间是否够用。
应用场景:弱引用非常适合存储元数据,例如:存储 ClassLoader 引用。如果没有类被加载,那么也没有指向 ClassLoader 的引用。一旦上一次的强引用被去除,只有弱引用的 ClassLoader 就会被回收
4、虚引用 PhantomReference
与前面引用不同, 虚引用的对象,在任何时间都可能被垃圾回收,它必须和引用队列联合使用,
当我们回收对象时,发现它还有虚引用,就会在内存回收前,将虛引用增加到引用队列中,
这时我们可以通过判断引用队列中,是否有相应的虚引用,然后在对象回收之前采取必须的措施。
引用队列
- 引用队列由 java.lang.ref.ReferenceQueue类来表示,它用于保存被回收后对象的引用。当把软引用、弱引用和引用队列联合使用的时候,系统在回收被引用的对象之后,将把被回收对象对应的应用添加到关联的引用队列中。与软引用和弱引用不同的是,虚引用在对象被释放之后,将把已经回收对象对应的虚引用添加它的关联引用队列中,这是得可以在对象被回收之前采取行动。
- 软引用和弱引用可以单独使用,但是虚引用不能单独使用,单独使用虚引用没有太大的意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已经包含了该虚引用,从而了解虚引用所引用对象是否即将被回收。