在JVM中,垃圾回收器作用的主要区域就是堆和方法区
(一)回收堆中的对象
JVM会把死去的对象最为即将回收的对象。那么JVM怎么先判断对象的状态是活着还是死去?JVM会把死去的对象最为即将回收的对象
引用计数算法
给每一个对象添加引用计数器,有栈中的引用指向该对象,该对象的引用计数器就加1,引用失效(栈中引用该对象的方法执行完)就减1。若一个对象的引用计数器为0就认为不能在使用,可以回收,
问题
当两个对象循环相互引用,且没有其他对象引用的时,引用计数器不为0但是不能回收,而且在这样的情境下,对象只有死去和活着的状态
可达性分析算法
通过一系列GC ROOTS的对象作为起点,从这些节点开始向下搜索,搜索走过的路径是引用链,一个对象到GC root直接没有可达路径,被认为是可回收对象。但是一个对象没有了指向GC roots的引用链,也不是非死不可,它可以自救。
不可达对象的自救过程
当发现一个对象没有可达路径,GC会对这个对象进行一次标记,并进行筛选,看该对象是否执行过finalize()方法或者对象是否覆盖了这个方法。如果已经执行或者没有覆盖finalize()方法,那么GC对该对象进行二次标记,它的状态就是死忙了。那么如果该对象覆盖了finalize()方法还没有执行,那么GC会触发对象去执行这个方法,也就是把这个对象放入F-QUEUE对列,但是GC不会等待队列中的对象执行完finalize()方法,把对象放入队列,过一会GC会在队列中选择对象进行二次标记。
所以要想自救的对象,就必须在进行F-queue队列之后执行完finalize()方法,重新与引用链上的对象建立联系,这样等到GC再来二次标记时,就会把它移除即将回收的集合。
那么什么样的对象可以是GC roots对象呢
(1)虚拟机栈中(局部变量表)中引用的对象
(2)方法区中类静态属性引用的对象
(3)方法区中常量引用的对象
(4)本地方法栈Native方法中引用的对象
类的普通成员变量不能作为GC roots
那么依据和原理是什么呢?
要明确的是必须以当前存活的对象集为Roots,因此必须选取确定存活的引用类型对象。虚拟机栈、方法区和本地方法栈不被GC所管理,因此选用这些区域内引用的对象作为GC Roots,是不会被GC所回收的。其中虚拟机栈中的栈帧和本地方法栈都是线程私有的内存区域,只要线程没有终止,就能确保它们中引用的对象的存活。而方法区中类静态属性引用的对象是显然存活的(类还在)。常量引用的对象在当前可能存活,因此,也可能是GC roots的一部分。
那么顺便通过一个程序,来说明到底程序中的变量和引用存放的位置。
首先类加载之后,类的信息以及静态变量以及编译时期的字面量和符号引用都放在常量池中
public class Test{
Test test= new Test();
//test位于这里时属于普通对象引用存放在虚拟机栈,但是不在方法中且程序中没有方法引用他,所以不在栈帧中,那么该引用指向的对象(new(Test)放在堆中)一般不是活着的状态,所以也不用做GC roots
public static Test t1;
//day是类的静态变量,存放在常量池中,与类的生命周期一直
int p=10;
//是类的普通变量,跟对象一起存放在堆中
public static void main(String args[]){
int date = 9;
t1=new Test(); //t1属于静态变量引用,这个时候new Test()对象可以作为GC roots;
//date是局部变量,且是基本数据类型,引用和值都放在栈帧中
Test test= new Test();
//test位于这里是局部引用,存放在栈帧中的局部变量表中,(new Test())对象存放在堆中,可以作为GC roots,线程正在调用
test.change(date);
BirthDate d1= new BirthDate(7,7,1970);
d1 为局部对象引用,存在栈帧中,对象(new BirthDate())存在堆中,其中d,m,y为局部变量存储在栈帧中,且它们的类型为基础类型,因此它们的数据也存储在栈帧中。当BirthDate构造方法执行完之后,d,m,y将从栈中消失。
}
public void change1(int i){
i = 1234;
//i为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失
}
main方法执行完之后,date变量,d1引用将从栈中消失,new Test(),new BirthDate()将等待垃圾回收
}
class BirthDate {
private int day;
private int month;
private int year;
//day,month,year为成员变量,它们存储在堆中(new BirthDate()里面)
public BirthDate(int d, int m, int y) {
day = d;
month = m;
year = y;
}
// 省略get,set方法………
}
引用的分类
在jdk1.2之后,对引用的概念进行重新定义分为
强引用:频繁出现的引用,如果有强引用指向该对象,垃圾收集器不会回收这类对象
软引用:指向有用但是非必须的对象,在内存溢出前,GC对它们进行二次回收
弱引用:也是非必须对象,比软引用弱一些,只能存活到下一次GC工作时,不管内存如何,只要还是只有弱引用指向的对象,GC就会回收
虚引用:最弱的引用关系,不能通过虚引用获得对象实例,对象有一个弱引用指向自己只是为了在GC回收它时受到一个系统通知。
(二)回收方法区
在方法区(或者有人叫永久代)中GC主要收集废弃常量和无用的类,回收效率远远低于回收堆的效率。
判断废弃常量
以字面量回收为例,有一个字符串常量"abc"在方法区的常量池,但是并没有String类型的引用指向他,就是废弃常量。如果这个时候GC开始工作,有必要的话就会回收这个常量,清理出常量池。
判断无用类
(1)该类的所有实例对象都被回收了
(2)加载该类的ClassLoader也被回收了
(3)该类对象的java.lang.Class对象也没有被引用,无法通过反射访问类的方法
同时满足这3个条件,GC就可以回收这个类,但不是一定会回收。可以通过参数设置