GC是java中比较有特色的技术,减轻了程序员的负担。当然也是面试中的高频话题。
对于垃圾回收,首先要解决的是找出哪些对象是需要回收的。
第一个方法是计算引用数目,实现比较简单,但是无法解决循环引用的问题。比方两个对象,互相引用对方,但是又没有其他对象引用他俩,在系统中,这两个对象其实是孤立的,应该被回收,但是引用书不为0。所以,java里面并没有采用这种方法。
第二种方法是基于图论的。对象之间的引用关系会形成一个图,只要遍历这个图,标记走过的对象即可。剩下的对象就是需要被回收的。那么,应该从哪些对象开始遍历,即起点是哪里?在虚拟机里面专门规定了起点,叫做gc root,如下:
(1)栈里面的引用;
(2)本地方法栈的引用;
(3)方法区的常量引用;
(4)方法区静态引用;
这些都是一次回收时的起点。
一旦搞清楚了哪些回收,就要思考怎么回收了。
第一种,标记-清除法,就是先标记,再清除标记的。缺点是,直接清除,那么会有间隙,形成了碎片,导致利用率不高。其次,相比后面的方法是连续清除的,这种逐个地清除效率也不高。
第二种,复制算法,把整个堆内存分为两部分,只使用其中的一部分,然后标记结束,把存活对象复制到另一块,把前一块整体回收,这种清除效率很高,只清除一次,但是如果存活的对象很多,复制成本太高,所以只适用于存活对象少的场景。
第三种,标记-整理法,也是先标记,然后把存活对象移到一端,意思是比方都依次按顺序移到起始位置,把最后一个位置之后的空间回收。
第四种,分代算法,其实是综合使用前三种方法。也是java中采用的方法。
把堆内存分为三种区域,新生代,老年代和持久代。新生代是绝大多数对象构造的位置,如果长时间存活移至老年代,持久代存放方法区的内容,比如类定义等信息。之所以分代是为了让各个算法应用到合适的场景。
新生代分三个区,绝大多数对象构造发生在这里,很大的数组直接在老生代。新生代的gc叫做minor gc,老生代的叫做major gc。新生代的三个区,一个是eden,两个survive,一个是from,一个是to。from和to是交替的。过程如下,新生代由于对象的生命周期都很短,所以采用标记-复制算法,复制清除的时候,会把存活对象复制到to区域,清除剩下的,然后两个survive区交换名字。这也是为什么要有两个survive区的原因。新生代每一次移动复制,对象会涨一岁,直到达到阈值,会被放到老生代。老生代可以采用标记-清除法,因为大多数都活的挺久。