JVM垃圾回收--对象判断生死

298 阅读5分钟

弄懂垃圾回收需要了解3个问题,第一,回收发生在哪里?第二,对象在上面时候可以被回收?第三,如何回收这些对象?

1、回收发生在哪里 jvm内存区域中,程序计数器,虚拟机栈和本地方法栈这三个区域是有线程私有的,随着线程的创建而创建,销毁而销毁;栈中的栈帧随着方法的进入和退出而进行入栈和出栈的操作,每个栈帧中分配的多少内存在类结构确定的时候就已经知道的,因此这3个区块的内存分配和回收都具有确定性。

2、对象在思明时候可以被回收? jvm如何判断对象是可以被回收的?一个ui想不再被引用,就表示该对象可以被回收。目前有2种算法可以判断对象是否可以被回收。

引用计数法:通过一个对象的引用计数器来判断该对象是否被引用了。当对象被引用,引用计数器就会加1,当引用失效,计数器减1,当对象的引用计数器为0时,就表示该对象不再被引用,可以被回收。计数算法实现简单,效率也高,但是存在对象之间相互循环引用的问题。

什么是对象之间相互循环引用?看下面的图片

obj1和obj2互相引用,因此无法被释放,最终进入死循环。

可达性分析算法:GC Roots是该算法的基础,GC Roots是所有对象的根对象,在JVM加载的时候,会创建一些普通对象引用正常对象。这些对象作为正常对象的起始点,在垃圾回收时,会从这些GC Roots向下搜索,当一个对象到GC Roots没有任何引用连相连接时,就正常此对象是不可用的。

如上图所示,Object1-4都可以到达GC Roots,证明其不可被回收,5,6没有到GC Roots的节点,因此可以被回收。

GC Roots对象:

虚拟机栈(栈帧中的本地变量表)中引用的对象

方法区中静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI引用的对象

标记算法,在可达性分析算法中不可达的对象,暂时处于待回收状态,一个对象真正被回收,要经过两次标记过程。

第一次标记并进行一次筛选。筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖此方法或者此方法已经被虚拟机调用过,虚拟机将这两张情况都视为“没有必要执行”,对象被回收

第二次标记,如果对象被判定为有必要执行finalize()方法,那么该对象会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但是并不承诺会等待它运行结束。这样的原因是,如果一个对象finalizer()方法中执行缓慢,或者发生死循环,将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。Finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中拯救自己,只需要中心将引用连上的任何一个对象建立关联即可,那将被暂时移除“即将回收”的集合。如果对象还没有建立连接,对象基本被回收。

Java中的引用分为以下4种:

3、对象如何回收,遵循两个特性

自动性:Java提供一个系统级别的线程来追踪每一块分配出去的内存空间,当JVM处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一块空间的内存块。

不可预期性:一但一个对象没有被引用了,该对象是否立刻被回收?很难确定一个没有被引用的对象是不是会立刻回收,因为有可能程序结束的时候,对象仍然在内存中。垃圾回收线程在JVM中自动执行的,Java程序无法强制执行。

GC算法,JVM提供了不同的回收算法来实现这套机制,有如下几种算法:

分析以下代码:

public class GcTest {
    public static GcTest text = null;
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method execute");
    }

    public void isAlive(){
        System.out.println("yes i am alive");
    }

    public static void main(String[] args) throws InterruptedException {
        text = new GcTest();

        //第一次GC
        text = null;
        System.gc();
        //gc比较慢,需要延时执行
        Thread.sleep(500);
        if(text !=null){
            text.isAlive();
        }else {
            System.out.println("i am died");
        }

        //第二次GC
        text = null;
        System.gc();
        //gc比较慢,需要延时执行
        Thread.sleep(500);
        if(text !=null){
            text.isAlive();
        }else {
            System.out.println("i am died");
        }
    }
}

运行结果:

finalize method execute

yes i am alive

i am died

从上面的代码可以看出,text对象确实自救过一次,并且在收集前成功逃脱了。另外有一点是,两端一样的代码,第一次成功逃脱,第二次却失败了,这是因为任何一个对象的finalize()方法都只会被系统自动调用一次,如果有下一次回收,finalize方法不会再被执行,因此第二次自救失败。