文章目录


1.如何判断对象可以回收

1.1.引用计数法

  • 当一个对象被其他变量引用,该对象计数加一,当某个变量不在引用该对象,其计数减一
  • 当一个对象引用没有被其他变量引用时,即计数变为0时,该对象就可以被回收

缺点:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放

JVM专题(十)-垃圾回收(一)_软引用

1.2.可达性分析算法

  • JVM中的垃圾回收器通过可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看能否沿着GC Root对象为起点的引用链找到该对象,如果找不到,则表示可以回收
  • 可以作为GC Root的对象
    – 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    – 方法区中类静态属性引用的对象
    – 方法区中常量引用的对象
    – 本地方法栈中JNI(即一般说的Native方法)引用的对象
    – 所有被同步锁(​​​synchronized​​关键字)持有的对象。

示例

/**
* 演示GC Roots
*/
public class Demo2_2 {

public static void main(String[] args) throws InterruptedException, IOException {
List<Object> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
System.out.println(1);
System.in.read();

list1 = null;
System.out.println(2);
System.in.read();
System.out.println("end...");
}
}

运行程序,分别生成垃圾回收前后的​​dunp​​文件

jps // 查看进程号
// format=b 生成文件格式为二进制;live主动触发垃圾回收,保留存活对象;file表示存放位置
jmap -dump:format=b,live,file=1.bin 进程号51125

list1置空前,生成dunmp文件1.bin
JVM专题(十)-垃圾回收(一)_垃圾回收_02

List<Object> list1 = new ArrayList<>();

​list1​​​是局部变量,存在于活动栈帧;​​new ArrayList<>()​​​产生的对象才是存在于堆中的对象。即此处​​new ArrayList<>()​​对应的那个对象才能作为根对象。

list1置空后,生成dunmp文件2.bin
JVM专题(十)-垃圾回收(一)_垃圾回收_03
因为在执行

jmap -dump:format=b,live,file=2.bin 51125

使用了live参数,主动调用了垃圾回收。由于​​list1​​​被置空,​​list​​对象无人引用,所以被垃圾回收了。所以在根对象中找不到了。

2.五种引用

JVM专题(十)-垃圾回收(一)_虚引用_04

强引用

只有所有 GC Roots对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

强引用对象回收
JVM专题(十)-垃圾回收(一)_虚引用_05

软引用

仅有【软引用】引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
可以配合【引用队列】来释放软引用自身

软引用对象回收
JVM专题(十)-垃圾回收(一)_垃圾回收_06
JVM专题(十)-垃圾回收(一)_虚引用_07
此时A2对象可能被回收。

  • A2对象仅仅只被软引用对象引用
  • 在执行GC时,内存空间不足了,才会被垃圾回收
  • 回收后,软引用对象本身可以通过进入引用队列进行释放

弱引用

仅有【弱引用】引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
可以配合【引用队列】来释放弱引用自身

弱引用对象的回收
JVM专题(十)-垃圾回收(一)_垃圾回收_08
此时A3对象可能会被回收

  • A3对象仅仅被弱引用对象引用
  • 当执行GC时,无论内存是否不足,都会被垃圾回收
  • 回收后,弱引用对象本身可以通过进入引用队列进行释放

虚引用

必须配合【引用队列】使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将【虚引用】入队, 由 Reference Handler 线程调用虚引用相关方法释放【直接内存】
如上图,B对象不再引用ByteBuffer对象,ByteBuffer就会被回收。但是直接内存中的内存还未被回收。这时需要将虚引用对象Cleaner放入引用队列中,然后调用它的clean方法来释放直接内存

虚引用对象的回收
JVM专题(十)-垃圾回收(一)_软引用_09
JVM专题(十)-垃圾回收(一)_虚引用_10
虚引用一般是对直接内存分配的应用。

  • 当声明​​ByteBuffer​​时,​​ByteBuffer​​会分配一块直接内存,并把直接内存的地址传递给虚引用对象Cleaner。
  • 当​​ByteBuffer​​不再被强引用时,被回收后,直接内存还没有被释放。这时会将虚引用放入虚引用的引用队列,由​​Reference Handler​​线程监控,发现虚引用对象,调用虚引用相关方法​​Unsafe.freeMemory​​释放直接内存。

终结器引用

无需手动编码,但其内部配合【引用队列】使用,在垃圾回收时,【终结器引用】入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过【终结器引用】找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
如上图,B对象不再引用A4对象。这时终结器对象就会被放入引用队列中,引用队列会根据它,找到它所引用的对象。然后调用被引用对象的finalize方法。调用以后,该对象就可以被垃圾回收了

终结器引用的回收

JVM专题(十)-垃圾回收(一)_软引用_11

  • 所有的类都继承自Object类,里面有一个终结方法finalize()方法,当对象重写了finalize()方法,且没有强引用引用它时,它就可以被当成垃圾进行垃圾回收。
  • 当对象没有被强引用时,会由jvm为该对象创建一个对应的终结器引用。当这个对象被垃圾回收时,会将终结器引用加入引用队列,但是对象不会被垃圾回收。
  • 再由一个优先级较低的Finalizer线程去监控引用队列是否有终结器引用,如果有,就通过终结器引用找到A4对象,调用其finalize()方法,等调用之后,等下一次垃圾回收时,就可以被垃圾回收了。
  • 工作效率低,第一次GC不会回收对象,先将终结器引用入队,等到第二次垃圾回收才有可能被回收。

6.代码示例

6.1软引用

# 虚拟机参数
-Xmx20m -XX:+PrintGCDetails -verbose:gc
/**
* 演示软引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo2_3 {

private static final int _4MB = 4 * 1024 * 1024;



public static void main(String[] args) throws IOException {
/*List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(new byte[_4MB]);
}

System.in.read();*/
soft();


}

public static void soft() {
// list --> SoftReference --> byte[]

List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());

}
System.out.println("循环结束:" + list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
}

JVM专题(十)-垃圾回收(一)_垃圾回收_12

/**
* 演示软引用, 配合引用队列
*/
public class Demo2_4 {
private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) {
List<SoftReference<byte[]>> list = new ArrayList<>();

// 引用队列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

for (int i = 0; i < 5; i++) {
// 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}

// 从队列中获取无用的 软引用对象,并移除
Reference<? extends byte[]> poll = queue.poll();
while( poll != null) {
list.remove(poll);
poll = queue.poll();
}

System.out.println("===========================");
for (SoftReference<byte[]> reference : list) {
System.out.println(reference.get());
}

}
}

JVM专题(十)-垃圾回收(一)_垃圾回收_13

6.2 弱引用

虚拟机参数

-Xmx20m -XX:+PrintGCDetails -verbose:gc
/**
* 演示弱引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo2_5 {
private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) {
// list --> WeakReference --> byte[]
List<WeakReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
list.add(ref);
for (WeakReference<byte[]> w : list) {
System.out.print(w.get()+" ");
}
System.out.println();

}
System.out.println("循环结束:" + list.size());
}
}

JVM专题(十)-垃圾回收(一)_虚引用_14