Java虚拟机 之 垃圾收集算法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/yichen97/article/details/90743639

引言

垃圾收集的学习的内容还算不少,放在一篇显得有点多,放两篇里感觉内容又都不算多,内容也无法界定成两部分,很纠结呐。但考虑到读两篇的感觉比一篇好的多,最起码是心理上,那就干脆分成两篇吧。

此篇为垃圾收集的第一篇。

如何判断对象为垃圾对象?

1. 引用计数法

引用计数器就是在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就加1;当引用失效(对象被置为空)的时候,计数器就减1。

比如说有这样一个对象,他的引用指向了对象区域,所以计数器就加1,计数器就为1。

当该对象被置为空,则这个引用就指向了NULL。

当垃圾回收器发现这个对象的计数器为0,这个对象就被回收了。

但是,这种垃圾回收算法现在基本上没怎么被垃圾回收器使用的,这是为什么呢?

我们来做一个这样的操作:先创建两个对象。

然后让这两个对象之间相互循环引用。

最后再将栈的引用置空,就完成了两个对象实则为垃圾,当两个对象的引用计数器都为1(都有一个引用),但引用计数法也无法将它回收。

具体操作如下:

public class Main{
	
    private Object instance;

    public static void main(String[] args) {
    	Main m1 = new Main();
    	Main m2 = new Main();

        //相互引用
    	m1.instance = m2;
    	m2.instance = m1;

    	m1 = null;
    	m2 = null;

        System.gc();   //手动回收
    }
}

接下来就是用eclipse测一下,该对象能否被回收?

将写好的Demo右键 -> RunAs -> RunConfigurations... -> Arguments -> VM arguments

在这儿用到两个参数,分别是:

-Verbose:gc     //简单信息

-XX:+PrintGCDetail   //详细信息

现在运行程序,从打印出来的日志来看,该对象确确实实显示已经被回收了。

如果想要显示更明显的话,可以在构造方法中故意占用一块较大的空间,看内存是否被回收了。因为所占用的内存被回收了,所以证明,jdk1.8中所使用的垃圾收集器不是引用计数法。

2. 可达性分析法

GCRoot,就是垃圾回收的根结点。通过它往下走,可以走到A、B、C(有引用链相连接);但走不到D,所以D就被标记为垃圾(没有引用链相连接)。

如果从GCRoot到A的引用被断开了,它们都无法被走到,则A、B、C、D都被标记为垃圾,都要被回收。

既然GCRoot这么重要,哪些地方可以作为GCRoot呢?

1. 虚拟机栈(栈帧中的局部变量表)。

2. 方法区的类属性所引用的对象。

3. 方法区中常量所引用的对象。

4. 本地方法栈中引用的对象。

只要以上这四处地方作为根结点,还无法找到的内存,都会被视为垃圾,被GC回收。

剩下的就是垃圾回收器是如何回收的?

常见的回收算法有:标记-清除算法;复制算法;标记-整理算法;分代收集算法。

垃圾回收算法:标记-清除算法

标记:一般根据可达性分析法,标记处所需要回收的对象。如果一个对象没有任何引用指向它,则这个对象就会被标记。

清除:把不用的对象给清除掉。

标记-清除算法现存在两个问题:

1. 标记和清除这两个过程的执行效率不高。

2. 标记和清除完成后,出现很多不可连续的内存空间,导致大对象无法存入的时候,需再次进行垃圾回收,消耗性能。

垃圾回收算法:复制算法

从第一个章节就说,线程共享区里有堆和方法区。

现在,为了方便垃圾的回收,堆内存被细分为新生代和老年代,而在新生代中,又被分成了Eden(伊甸园)、Survivor(存活区)、Tenured Gen。

假如说下面是一块堆内存,复制算法将它分成了两块,但是使用的时候只使用其中一块。

比如现在需要进行一次垃圾回收,要将被标记的(红色)回收掉。

然后将不被回收的复制到另外一块区域里,给他排列好,那么,这块内存就是连续的了,最后再将这块区域给清空掉。

接下来再分配空间在新的区域里面分配了,依次类推,循环反复。

上面这个做法提高了执行效率,但有引入了一个新的问题:由于内存资源只使用一半,所以对内存资源造成了极大的浪费。

所以就使用分代的方式去解决这个问题。

当一个对象创建的时候,会先进入Eden区域。

当Eden满了,在垃圾回收的时候,会讲Eden区存活的对象移到Survivor(B),然后将Survivor(A)和Eden一起清掉。

下一次创建对象的时候,会把Eden区域存活的对象接着往Survivor(B)里存放,并且检测之前存放的对象是否依然存活。接着,将Survivor(B)里存活的对象和Eden里存活的对象都移到Survivor(A)里去,清掉Survivor(B)和Eden区,就完成了垃圾回收。

内存分配担保:如果Eden区存活的对象超过了Survivor,也就是说超过了10%很多,导致Survivor区就放不开了,那对象就扔到老年代里面去。

垃圾回收算法:标记-整理算法

标记-整理算法,可以理解为标记-整理-清除这样一个过程。

由于老年代的内存又着回收效率低、对象存活率高的特点,上面的算法已经不适用了,标记-整理算法是专门针对老年代的一种算法。

它的思想就是被定义为垃圾的对象向内存的一端去移动;存活的对象向另一端移动,直接就可以把需要回收的那一块清除掉就可以了。

垃圾回收算法:分代收集算法

分代收集算法是 标记-整理算法 和 复制算法 的一个结合。

直接使用分代收集算法,他会根据新生代和老年代的特点(内存的分代),去选择不同的垃圾回收算法。比如说他会在新生代使用复制算法,在老年代使用标记-整理算法。

猜你喜欢

转载自blog.csdn.net/yichen97/article/details/90743639