垃圾回收的方式
1. 引用计数法
表示在底层实现的时候使用一个用于计数的变量进行计数,在每一次对一个变量或者实例进行引用之后,计数加一,当引用不使用之后,计数减一,直到计数为零的时候,这就代表着当前的这个变量或者实例没有被引用,也就是说可以被当成垃圾进行回收。
在这里,也会存在一个弊端,就是循环引用,在A和B对象之间一直互相引用,计数一直增加,就无法确定在什么时候才能进行回收。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL1UFVNp3YU9EMRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0gzM3EzMwETMwIDMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
2. 可达性分析算法
Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
扫描堆中的对象,看是否能够沿着 GC Root 对象为起点的引用链找到该对象,找不到,表示可以回收
那怎么查看这个GC root 有哪些呢,我们使用一段简单的代码进行测试,在这里检测两个状态,运行代码。
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class GC_root {
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...");
}
}
使用jmap工具抓取进程快照,使用命令 jmap -dump:format=b,live,file=1.bin 8208
- b 表示二进制文件
- live 当前还存活的对象,也就是会触发一次垃圾回收
- file 表示将快照抓取出来,另存为一个什么文件
- 最后面表示当前进程id
使用命令进行检测
得到了两个文件,但是怎么进行分析呢,在这里我们使用eclipes提供的MAT工具
MAT内存分析工具
MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。可以用于查找内存泄露以及查看内存消耗情况。MAT是基于Eclipse开发的,是一款免费的性能分析工具。可以在http://www.eclipse.org/mat/下载并使用MAT。
下载完成之后进行解压缩,双击exe文件打开。之后打开这两个bin文件,如下图所示就可以查看gc root
打开GC root 查看,分了四个大类:如下图所示:
3. 四种引用
-
强引用
只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
-
软引用(SoftReference)
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
可以配合引用队列来释放软引用自身
-
弱引用(WeakReference)
仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
可以配合引用队列来释放弱引用自身
-
虚引用(PhantomReference)
必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
-
终结器引用(FinalReference)
无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象
4. 软引用案例
在这里,我们获取数据,但是我们的内存有限,而这里的资源又不是那么重要,(添加一个参数 -Xmx20m 进行模拟)我们使用强引用的话,会出现堆内存溢出的报错。使用以下代码测试
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]);
}
使用软引用进行实现:添加一个软引用,在这里的连接引用也是由一个强引用之间加上了一个软引用。
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());
}
}
运行代码,添加了软引用之后,内存不足的时候会触发垃圾回收机制,所以在这里代码是不会导致堆内存溢出的。垃圾回收之后,list当中的值会被清除为null。
软引用与引用队列
在这里软引用虽然为null,但是也会占据内存空间,在这里我们使用一个引用队列对这个软引用进行清除。代码实现如下:
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
public class queue {
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());
}
}
}
在最后获取这个list就只会存在一个数值,其余的null值均被清空。
弱引用
弱引用对象我们和前面的测试一致,一直添加,并且我们给虚拟机加上参数 -XX:+PrintGCDetails -verbose:gc 表示打印垃圾回收的相关信息。
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class WeakReference_test {
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());
}
}
运行代码,查看结果(在这里,我们也可以将-Xmx设置的小一点,这样的话垃圾回收机制就会被触发多次)在这里没有设置,在第七次执行后,第八次执行时,内存不够就会调用垃圾回收机制,将前面没有用的变量所占的内存进行回收,