对象死了吗?
堆中的对象是否应该被回收,首先需要去确认下这个对象是存活还是死亡。如何判断?
引用计数法
引用计数法即为每个对象添加一个引用计数器,每当有一个地方引用时,计数器++,当引用失效时,计数器–,最终计数器为0时说明对象已经无人引用,即可以被回收。
引用计数法实现逻辑简单,效率也很高,但是主流的java虚拟机没有选用该算法进行内存管理的,原因是无法解决循环引用的问题。
如:对象A引用B,对象B同时引用A,除此之外没有其他地方引用,实际上这两个对象不可能再被访问,但是因为引用计数器不为0导致无法被回收。
可达性分析算法
通过一系列的“GC Roots”的对象作为起点向下搜索,搜索经过的路线称为引用连,不在引用连上的对象称为不可达对象,即可以被回收的对象。
GC Roots:
1 虚拟机栈中引用的对象(实际上就是方法中定义的引用。换句话说就是方法中定义的对象,在方法出栈前,其引用连的对象都不能被回收)
public class Sort {
static Sort s;
public static void main(String[] args) {
// sort 即为 GC Roots
Sort sort = new Sort();
// 当sort 置为null,此时堆中的Sort对象失去了GC Roots 的引用,则会被回收
sort.s= new Sort();
sort=null;
}
}
2 方法区类静态属性引用的对象
public class Sort {
static Sort s;
public static void main(String[] args) {
// sort 即为 GC Roots
Sort sort = new Sort();
// 由于s是今静态变量,因此sort引用被回收后,s指向的对象不会被回收。
sort.s= new Sort();
// 当sort 置为null,此时堆中的Sort对象失去了GC Roots 的引用,则会被回收
sort=null;
}
}
3 方法区中常量引用的对象(spring 中的对象放到三级缓存中,由于三级缓存是final类型的因此可以作为GC Roots,因此sping中的单例bean对象不能被回收)
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//第2级缓存 用于存在已经实例化,还未做代理属性赋值操作的 单例BEAN
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
//第3级缓存 存储创建单例BEAN的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
4 本地方法栈中引用的对象(一般说的是Native方法引用的对象)
当调用java方法时,虚拟机会创建一个栈帧并压入该线程的栈中,当调用的是本地方法时,并不会压入栈中,而是通过动态链接的形式指向本地方法栈。
指向本地方法栈对象的引用连上的对象不会被回收
引用
不管是引用计数法还是可达性分析算法,最终起作用的都是引用。
引用分为:强引用,软引用,弱引用,虚引用。
强引用:new出来的对象所对应的就是强引用。Object o= new Object(); o就是强引用,垃圾收集器永远不会回收掉被强引用引用的对象。
软引用:有用但非必须的对象。系统在发生内存溢出之前就会把软引用关联的对象回收掉,若内存还不够,则跑出内存溢出的异常。
弱引用:描述非必须的对象,比软引用更弱一些,只被弱引用指向的对象只能活到下一次垃圾收集之前。无论内存是否充足,垃圾收集时都会回收这类对象。
Thread类中的内部类ThreadLocalMap中的key就是若引用。当threadLoca引用失效后,其实例就只有弱引用关联着,因此会被回收,造成内存泄露。
虚引用:最弱的引用。无法通过虚引用获取对象的实例。设置虚引用的唯一目的就是能在这个对象被回收时收到一个系统的通知。
生存还是灭亡
可达性分析算法中不可达的对象,只是判了“缓刑”,并不是非死不可。虚拟机会对不可达的对象进行一次标记 并进行一次筛选,筛选的条件是对象有没有必要
执行finalize()方法,如果此对象没有覆写finalize()方法或者经执行过finalize()方法,那么此对象就失去了最后一次自救的机会。如果有必要执行finalize()方法,那么
此对象会被放到一个F-Queue队列中,稍后会有虚拟机创建一个优先级低的线程会执行,但是不能保证会执行成功(防止finalize方法进入死循环或者异常,导致队列的其他对象
永久处于等待状态,一个对象执行一次finalize方法)
public class FinalizeTest {
public static FinalizeTest SVAE = null;
public void isAlive(){
System.out.println("still alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize execute");
FinalizeTest.SVAE = this;
}
public static void main(String[] args)throws Exception {
SVAE = new FinalizeTest();
SVAE = null;
System.gc();
// 稍微等一会 finalize方法的优先级很低
// 第一次自救 成功
Thread.sleep(1000);
if(SVAE == null){
System.out.println("dead");
}else {
SVAE.isAlive();
}
SVAE = null;
System.gc();
// 稍微等一会 finalize方法的优先级很低
Thread.sleep(1000);
// 第二次自救 失败 因为一个对象的finalize方法只能执行一次
if(SVAE == null){
System.out.println("dead");
}else {
SVAE.isAlive();
}
}
}
本文介绍了Java垃圾回收的两种主要算法:引用计数法和可达性分析算法。虽然引用计数法由于循环引用问题未被采用,但可达性分析算法通过GCRoots确定对象存活状态。同时,文章讨论了四种引用类型及其对对象存活的影响,以及对象在GC中的 finalize() 方法调用过程。通过对FinalizeTest的示例,展示了对象的自救机会。
806

被折叠的 条评论
为什么被折叠?



