Skip to content

垃圾回收机制

使用Java多了,只会new但是还没仔细研究过Java的垃圾回收,这篇文章,就是浅谈java和其他语言(C++)的垃圾回收。 严格来说程序是需要自己管理内存的,但是Java的jvm帮我们做了这些,这个也是Java和C++的最大不同,C++中所有的内存必须 回收如果没有回收那么就是内存泄露了,Java并不是这样,Java在内存充足的时候不会进行垃圾回收,而随着程序的运行结束,Java所占用 资源也会全部释放给操作系统,整个程序运行中也不会进行垃圾回收。

C++中有一个用于垃圾回收的析构函数,C++销毁对象的时候必须使用析构函数,这个可以用来做一些工作,这个意味着对象被放弃的时候一定会使用到,但是Java中即使你将对象置为null,也不一定会使jvm进行垃圾回收。Java中的finalize()就是用于在垃圾回收的时刻进行清理工作的,这个方法调用之后并不会导致内存被立即释放,下一次垃圾回收的时候这个对象才真正的被释放。

下面是一个例子,当内存被占用的时候finalize函数被调用了(也可以使用system.gc()来强制垃圾回收),但是如果程序没有将内存占满,那么函数不会被调用。C++中使用delete来操作某个对象析构函数一定会被调用。

java
public class TestFinalize {
    TestFinalize(long l){
        this.a = l;
    }

    //占用内存
    private long a;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(this.a+"   执行finalized");
        super.finalize();
    }

    public static void main(String[] args) {
        TestFinalize testFinalize = new TestFinalize(0L);
        testFinalize =null;
        long i=1;
        while(true){
            //不断创建String对象来占用内存
            String s = "" + i;
        }
    }
}
/**
* 输出 0   执行finalized
*/
public class TestFinalize {
    TestFinalize(long l){
        this.a = l;
    }

    //占用内存
    private long a;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(this.a+"   执行finalized");
        super.finalize();
    }

    public static void main(String[] args) {
        TestFinalize testFinalize = new TestFinalize(0L);
        testFinalize =null;
        long i=1;
        while(true){
            //不断创建String对象来占用内存
            String s = "" + i;
        }
    }
}
/**
* 输出 0   执行finalized
*/

垃圾回收只和内存有关,上面的测试代码并不合格,因为finalize这个函数体中的主要内容不是内存回收,不过这里要特别注意一点, 无论一个对象中是否含有其他对象都不用在finalize中指定内存回收行为,垃圾回收会释放对象占用的全部内存,finalize只有一种特殊的用途。

就是本地方法的特例,本地方法就是使用其他语言(C/C++或者其他)创建的方法,这些方法中可能会分配内存,然后这些内存必须释放,否则会导致内存泄露。而在finalize中调用类似c语言中的free函数是保证程序稳定运行的关键。

终结条件的应用

由于finalize并不一定会被使用,所以这个方法并不是常用的,也是比较不推荐使用的,但是有一个场景下这个还是有点用的,那就是终结条件的验证,也就是对象被清理的时候要求一定要怎样,但是对象不被清理的时候没必要验证,比如要求对象清理之前必须存入数据库中,如果有这个要求,可以加一个判断,这个可以检测系统有没有缺陷,这时候可以主动调用system.gc()来强制jvm进行垃圾回收,也可在以后的系统运行中排查出这种故障。

垃圾回收器的几种工作方式

C++语言的内存管理模式像是一个空间有限的广场,广场上的每一个人都管理自己,而且为了提高空间利用率一般人做完了事情求要离开广场,新来的人就要占领他的位置,Java的内存管理类似于流水线,分配一个对象就前面移动一格(只是比喻,实际上中间夹杂着gc重新排列对象),当资源耗尽的时候释放内存并将剩下的对象集中排列在一起(GC作用)。

垃圾回收的引用计数机制

引用计数是这样的一种机制就是每个对象会有一个计数器,当有引用连接到对象,计数加一,引用置为null计数减一,然后垃圾回收就遍历所有对象,释放引用计数的为0的对象的内存。不过这个有个互相引用的问题,会极大影响效率。目前这种机制应该没有应用到jvm中。

一种更快的模式是基于活得对象机制的

如果对象是活得,那么一定会有一个可以被访问的引用链条,那么从堆栈和静态区遍历所有引用直到找到对应的对象,遍历完成之后就可以开始清理那些没有引用的对象,这个也解决了交互自引用的对象组问题。

这种模式下,java虚拟机采用一种自适应的垃圾回收技术,至于如何找到存活的对象取决于不同的jvm实现,这个也是自适应的垃圾回收的一部分,jvm主要有几种工作模式下面简单介绍一下这几种方式。

jvm gc实现

实现一:停止-复制

这个方式是先暂停程序然后将当前的堆转移到另一个堆,剩下的全是垃圾,这个过程中需要重新映射内存地址,这个方式有两个缺点:在两个堆之间来回倒腾,这将需要原来占用内存的两倍,然后的问题是,当程序运行起来之后,有很多东西是不变的,也就是程序只有一部分需要清理,一部分不需要清理,但是复制,是不会考虑到这些的,这将会导致很大的浪费,这时候垃圾回收器的作用将会体现出自适应的优越性,它从原来的模式转化为另一种机制:标记-清扫模式。

实现二:标记-清扫