为什么会有四种引用
我们先回顾下在 Java 虚拟机内存体系(1) 中提到了的垃圾回收算法
1、引用计数法
原理:给对象添加一个引用计数器,每当有一个地方引用它,计数器的值就加一。每当有一个引用失效,计数器的值就减一。当计数器值为零时,这个对象被认为没有其他对象引用,可当作垃圾回收。
缺点:需要维护引用计数器,有一定的消耗。且较难处理循环引用的问题。(现在基本没有地方使用这种算法了,了解即可)。
2、可达性分析算法
先想象一下生活中的场景,假设张三这个人有一条狗,狗被系着一条绳子。如下图所示:
如果张三把狗遗弃了,那么这条狗和绳子就没有关联关系了,狗会被抓狗队抓起来送到宠物收容所。这里可以把张三当作 GC Roots,张三和狗之间断了联系,那么狗和绳子都会被回收。如下图所示:
可达性分析算法原理:通过一系称为 GC Roots
的对象作为起始点,从 GC Roots
的对象出发,向下搜索,如果找到的对象和 GC Roots
有直接引用或间接引用关系,则说明这个对象不是垃圾,否则,这个对象就是垃圾。
Java 中对引用的传统定义:如果 reference
类型的数据中存储的数值代表是另外一块内存的起始地址,就称 reference
数据是代表某块内存、某个对象的引用。
但是这种定义比较简单,只有被引用和未被引用 两种状态。如果有些对象没什么用,但是又不想丢弃,就让它留在内存中,当内存紧张时再回收,这样有个好处,当想再用它的时候,就不用再创建一个新的出来,这个就是我们常说的将对象缓存起来。很多系统的缓存功能都符合这样的应用场景。
强引用
Cat cat = new Cat()
我们可以看到等号的右边,创建了一个对象,该对象存储在堆中,而等号的左边是这个对象的引用,存储在栈中。而这种引用关系,就被称为强引用。
当 JVM 开始回收的时候,对于强引用,即使内存不足也不会回收被强引用的对象。因此强引用是造成 Java 内存泄漏的主要原因之一。
而如果我们显示地将强引用赋值为 null(比如 cat = null),则可以认为之前的对象会被垃圾收集器回收。
编写脚本
package com.test;
public class TestStrongReference {
public static void main(String[] args) {
// 这样定义的默认就是强应用
Object obj1 = new Object();
// 使用第二个引用,指向刚刚创建的 Object 对象
Object obj2 = obj1;
// 将 obj1 引用设置为 null
obj1 = null;
// 模拟 OOM 自动 GC
try {
// 创建 20M 的大对象
byte[] bytes = new byte[20 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(obj1);
System.out.println(obj2);
}
}
}
运行程序
$ java -Xms5m -Xmx5m -XX:+PrintGCDetails com.test.TestStrongReference
[GC (Allocation Failure) [PSYoungGen: 918K->496K(1536K)] 918K->740K(5632K), 0.0016865 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation F