1. 如何判断对象可以回收
2. 垃圾回收算法
3. 分代垃圾回收
4. 垃圾回收器
5. 垃圾回收调优
1. 如何判断对象可以回收
1.1 引用计数法
对象有一个记录引用个数的计数,只要其他变量引用该对象,计数就加1。当变量不再引用它,计数就减1。计数为0,表明该对象不再被引用,可作为垃圾回收。
但是该计数法有一个弊端,就是产生循环引用,如下图中,AB对象各自引用对方,引用计数都不为0,即使没人再引用他们俩,但是还是不能被回收,造成内存泄露。
1.2 可达性分析算法
可达性分析算法首先要确定一系列根对象。
● Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
● 扫描堆中的对象,看是否能够沿着 GC Root 对象 为起点的引用链找到该对象,找不到,表示可以回收
首先确定一系列根对象,所谓根对象,可以理解为那些肯定不能被垃圾回收的对象。在垃圾回收之前,首先会对堆中的所有对象进行一次扫描,查看某个对象是不是直接被根对象直接或间接引用,如果是,则不能回收,不是,则可以回收。
● 哪些对象可以作为 GC Root ?
1.2.1 通过MAT工具查看堆内存中可作为GC root的对象有哪些
演示代码
//演示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...");
}
注意:
注意区分引用变量和对象,如list1只是一个引用,他是存储在活动栈帧里的,他是一个局部变量,他后面引用的对象是存储在堆里的,根对象也是指堆中的对象,而不是引用变量
先把堆内存的当前状态转储成一个文件,jmap -dump:format=b,live,file=1.bin $pid(b是抓取成二进制文件,live是指只抓取存活对象,被回收的对象不抓取,live在抓取之前会主动触发一次垃圾回收,file表示存的哪个文件)
1、list1置空之前:
(1)System Class ,系统类,由启动类加载器加载的类,都是核心的类,一定不会被回收。
(2)Native Stack,包含操作系统引用的一些Java对象。Java虚拟机在进行方法调用时,必须执行一些操作系统的方法,而操作系统需要引用一些Java对象
(3)Busy Monitor,已被加锁的对象,也可作为根对象
(4)Thread,活动线程,其对象不可被回收,线程运行时都由一次次的方法调用组成,每次方法调用都会产生一个栈帧,栈帧内使用的东西都可以作为根对象。
如活动线程执行过程中局部变量引用的对象都可以作为根对象,如ArrayList,方法参数args引用的字符串数组对象也是根对象。
2、list1置空之后:主线程内已无ArrayList,局部变量置为null,表示不再引用ArrayList对象。然后执行jmap -dump:format=b,live,file=2.bin 2024,触发GC,被GC垃圾回收掉,所以在根对象列表中无法找到他。
1.3 四种引用
1. 强引用
● 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
实际上平时用的所有的引用都是强引用,如通过=运算符把新建的对象赋值给一个变量,称这个变量强引用了这个对象。强引用的特点是:只要沿着GC Root的引用链能找到他,就不会被垃圾回收。B对象和C对象都强引用了A1对象,当GC Root对他的强引用都断开时,才可以被垃圾回收。称之为强引用。
2. 软引用(SoftReference)
● 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用
对象
● 可以配合引用队列来释放软引用自身
只要对象没有被直接的强引用所引用,在垃圾回收时,都可以被回收掉。如A2对象有一个软引用引用他,被B对象直接强引用,如果B对象不再引用他,则可以在垃圾回收发生时,被垃圾回收掉。
3. 弱引用(WeakReference)
● 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
● 可以配合引用队列来释放弱引用自身
软引用是相同前提条件下内存不足时才会再次触发垃圾回收,回收掉软引用对象。
软、弱引用还可配合回收队列工作,当软引用的对象被回收掉(这里例如A2对象被回收掉),软引用本身是一个对象,他会进入引用队列,弱引用对象也是如此。他们自身也要占用内存,如果想要释放他俩,则需要使用引用队列找到他俩,依次遍历释放。
4. 虚引用(PhantomReference)
● 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象(这里指ByteBuffer)回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法(Unsafe.freeMemory())释放ByteBuffer申请的直接内存
5. 终结器引用(FinalReference)
● 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象
暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize
方法,第二次 GC 时才能回收被引用对象
1.4 软引用应用
软引用通常使用在什么场景,来看这样一个案例。
案例代码
package cn.itcast.jvm.t2;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* 演示软引用 后两个参数表示打印垃圾回收的详细参数
* -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() {