垃圾收集器(G1):首先它是面向服务端的,同时它是一种不会产生内存碎片的垃圾回收,类似于“标记-整理算法”,使用G1收集器时,Java堆得内存布局就与其它收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。G1垃圾收集器的运行过程大致为:
1)初始标记(Initial Marking)
2)并发标记(Concurrent Marking)
3)最终标记(Final Marking)
4)筛选标记(Live Data Counting and Evacuation
提gc算法之前,插一句嘴,我们先了解了解jvm中垃圾回收的判定,以前是属于引用计数算法,引用计数算法的原理是这样的:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;在任何时刻计数器的值为0的对象就是不可能再被使用的,也就是可被回收的对象。(但同时这种算法也会带来一个问题就是对象之间相互循环引用的问题,比如父类对象与子对象相互引用),然后就引出了现在所使用的可达性算法:
在上图中,objectA、objectB、objectC是可达的,不会被回收;objectD、objectE虽然有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
在Java中,可作为GC Roots的对象包括下面几种:
1、虚拟机栈中引用的对象;
2、方法区中类静态属性引用的对象;
3、方法区中常量引用的对象;
4、本地方法栈中Native方法引用的对象。
说完这些就可以回过头来看看gc算法了
首先是最基础的,“标记清除算法”:(空闲列表算法?)
A.标记阶段:标记的过程其实就是前面介绍的可达性分析算法的过程,遍历所有的GC Roots对象,对从GC Roots对象可达的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象;
清除阶段:清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header信息),则将其回收。
当然这种算法随之带来的问题就是,效率低下,和可能会出现内存碎片问题。
为了解决这种问题,
B.引入了复制算法:将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉。不过这种算法带来的问题就是,可能会造成大量的内存浪费,将内存缩小为原来的一半,浪费了一半的内存空间,代价太高,而且极其耗费时间,所以这种算法也是不是可取的,然后进行进一步的优化。
C.这个时候“标记-整理”(碰撞指针算法?)算法出现了,标记/整理算法的标记过程任然与标记/清除算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。最后给三个算法来个排序:
效率:复制算法 > 标记/整理算法 > 标记/清除算法(标记/清除算法有内存碎片问题,给大对象分配内存时可能会触发新一轮垃圾回收)
内存整齐率:复制算法 = 标记/整理算法 > 标记/清除算法
内存利用率:标记/整理算法 = 标记/清除算法 > 复制算法
还有一种终极分代算法,取各家之所长,如果有兴趣的朋友可以自己去查询相关知识,这里就不加详述了。
目前基本使用的是分代收集算法(以三种算法结合新生代和老生代的综合运用,新生代复制算法,老生代标记整理于清除)
文章目前比较浅显,后面补充