深入理解java虚拟机学习笔记(三)垃圾收集器与内存分配策略
一、概述
1、如何判定对象为垃圾对象?
引用计数法和可达性分析法
2、如何回收?
回收策略:标记-清除算法、复制算法、标记整理算法、分代收集算法
垃圾回收器:Serial、Parnew、Cms、G1
3、何时回收?
二、判定对象是否为垃圾对象的算法
1、引用计数法:
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1.
引用计数器实现简单,判定效率很高,但是很少使用。java虚拟机中没有使用,原因是它很难解决对象之间相互循环引用的问题。
2、可达性分析法
通过“GC Roots”的对象作为起始点,从这些结点向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
作为GC Roots的对象:
虚拟机栈中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中引用的对象。
三、垃圾收集算法
1、标记清除算法
算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。标记过程一般使用可达性分析法。
不足:
效率问题:标记和清除的效率都不高;
空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次的垃圾收集动作。
2、复制算法
复制算法是为了解决效率问题而提出。它将可用内存按容量分为大小相等的两块,每次只使用一块。当一块内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
不足:内存浪费。
堆内存中新生代采用复制算法。将内存划分为Eden和两块Survivor空间,分配比例8:1:1
3、标记-整理算法
复制算法主要针对新生代内存。老年代采用的是标记-整理算法。
标记整理算法,标记过程与标记清除算法一样,后续步骤不是直接对可回收对象进行整理,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。实际上就是标记-整理-清除过程。这样使得算法效率比标记-清除算法更高。
4、分代收集算法
根据对象存活周期的不同将内存划分为几块,一般是把java堆分为新生代和老年代。
新生代采用复制算法,老年代采用标记-清除或者标记-整理算法。
四、垃圾收集器
垃圾收集器是内存回收的具体实现,java虚拟机规范中对垃圾收集器的实现没有任何规定,因此不同厂商、不同版本的虚拟机所提供的垃圾收集器可能有很大差别,并且一般都会提供参数供用户根据自己应用特点和要求组合各个年代所使用的收集器。
jdk1.7正式提供了商用的G1收集器。
1、Serial收集器
Serial收集器是最基本、发展历史最悠久的收集器。
是一个单线程收集器。
桌面应用使用。
2、ParNew收集器
ParNew收集器是Serial收集器的多线程版本。
3、Parallel Scavenge收集器
采用复制算法(新生代收集器);
是多线程收集器。
达到一个可控制的吞吐量(ParNew与Parallel不同的地方)
吞吐量:cpu运行用户代码的时间与cpu消耗的总时间的比值
吞吐量=(执行用户代码时间)/(执行用户代码的时间+垃圾回收所占用的时间)
Parallel收集器提供了两个参数用户精确控制吞吐量:
-XX:GCTimePatio 直接设置吞吐量大小参数
-XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间参数
4、CMS收集器
CMS收集器是基于标记-清除算法实现的,工作过程如下:
初试标记、并发标记、重新标记、并发清除。
优点:
并发收集、低停顿
缺点:
占用大量的CPU资源、无法处理浮动垃圾、出现Concurrent Mode Failure、空间碎片
5、G1收集器
步骤:初始标记、并发标记、最终标记、筛选回收
优势:并行与并发、分代收集、空间整合、可预测的停顿
五、内存分配策略
1、对象优先分配到Eden
2、大对象直接分配到老年代
3、长期存活的对象分配到老年代
4、空间分配担保
5、动态对象年龄判断