垃圾回收算法及收集器介绍
一、垃圾回收要解决的四个问题
1、要回收哪些对象?
2、如何回收
3、回收过程中的内存空间如何进行管理
4、用什么样的过程进行回收
二、判断哪些对象可回收?
1、引用计数器
实现方案:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一,任何时刻当计数器为零的对象就不可能在被使用。
存在的问题:对于存在循环引用的情况下,引用计数器就无能为力
2、可达性分析
概念:当前主流的商用程序语言(Java、C#)的内存管理子系统,都是通过可达性分析算法来判断对象是否存活的。该算法的基本思路是从一系列称为“GC Roots”的跟对象作为起始点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为引用链。如果一个对象到GC Roots没有任何引用链相连或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可用的。
在Java技术体系里面,固定可作为GC Roots的对象包含以下几种:
在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
在方法去中类静态属性引用的对象,譬如Java类的引用类型静态变量
在方法去中常量引用的喜爱那个,譬如字符串常量池(String Table)里的引用
在本地方法栈中JNI引用的对象
Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
所有被同步锁(synchronized关键字)持有的对象。
反映Java虚拟机内部情况的JM XBean、JVM TI中注册的回调、本地代码缓存等。
三、垃圾回收算法
1、分代收集理论
分代手机理论是建立在两三个分代假说之上:
弱分代假说:绝大多数对象都是朝生夕灭
强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
跨代引用假说:跨代引用相对于同代引用来说仅占极少数
Java堆中会划分为新生代(Young Generation)和老年代(Old Generation)两个区域
2、标记-清除算法
定义:该算法分为“标记”和“清除”两个阶段
标记:首先标记出所有需要回收的对象;
清除:在统一回收掉所有被标记的垃圾对象
主要缺点:
执行效率不稳定,标记和清除这两个过程的执行效率都随对象增长而降低
内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片
3、标记-复制算法
定义:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。每次都是针对整个搬去进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,而且每次都是针对整个搬去进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况。实现简单,运行高效
缺点:这种复杂算法的代价是将可用内存缩小为原来的一般,空间相对比较多,此外在对象存活率较高时就要进行较多的复制操作,效率将会降低。
现在的商用Java虚拟机大多数有限使采用了这种手机算法去回收新生代,IBM公司曾有一项住啊们研究对新生代“朝生夕灭”的特点做了更量化的诠释 --- 新生代中的对象有98%熬不过第一轮手机,因此并不需要按照1:1的比来划分新生代的内存空间
改进后的复制算法 --- Appel式回收
定义:将新生代分成一块较大的Eden空间和两块较小的Servivor空间,每次分配内存只使用Eden和其中一块Survivor,发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Survivor空间上,然后直接清理掉Eden和已用过的Survivor空间。此外Appel式回收还有一个充当罕见情况的“逃生门”的安全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保。
4、标记-整理算法
针对老年代对象高存活率,并且无额外的空间可进行分配担保的情况下,提出了有针对性的“标记-整理”算法;整理的过程:不直接对可回收对象进行清理,而是让所有存活的队形都想内存空间的一段移动,然后直接清理掉边界以外的内存。
缺点:对于老年代这种存活对象较多的区域,移动存活对象并更新所有引用这些对象的地方将会是一种较为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行(Stop The World)
四、垃圾收集器
算法是内存回收的方法论,那么垃圾收集器是内存回收的实践者
1、Serial收集器
这个收集器是一个单线程工作的收集器,单它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或者一个收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
下图展示了Serial/Serial Old收集器的运行过程
2、ParNew收集器
ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其他行为跟Serial收集器一样。
3、Parallel Scavenge收集器
专门针对新生代的收集器,它同样是基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器,功能上与ParNew比较相似
特定:Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,他的目标是达到一个可控制的吞吐量
4、CMS收集器 (Concurrent Mark-Sweep)
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,基于标记-清除算法实现
整个来及回收过程分为四个步骤
1)初始标记 - 标记GC Roots能直接关联到的对象,但是需要Stop The World
2)并发标记 - 从GC Roots的直接关联对象开始遍历整个对象图的过程,不需要暂停用户线程,可以与垃圾收集线程一起并发运行
3)重新标记 - 修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
4)并发清除 - 清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活的东西,所以这个阶段也可以与用户线程同时并发
5、G1收集器
面向堆内存任何部分来组成回收集进行回收,衡量的标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
G1把连续的Java堆划分为多个大小相等的独立区域,每个Region都可以根据需要,扮演新生代的Eden区,Survivor空间或者老年代空间,收集器能对扮演不同角色的Region采用不同的策略去处理,G1收集器运行过程示意图如下:
对于垃圾收集器而言:
并行(Parallel):并行描述的是多条垃圾收集器线程之间的额关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态
并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行,由于用户线程未被冻结,所以程序仍然能相应服务请求,单由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。
评论