对于一般Java程序员开发的过程中,不需要考虑垃圾回收。
如何判定对象为垃圾对象;
引用计数法
可达性分析法如何回收垃圾对象;
回收策略(标记清除、复制、标记整理、分带收集算法)
常见的垃圾回收器(Serial、Parnew、Cms、G1)何时回收垃圾对象。
判断垃圾对象
可达性分析法
此算法的核心思想:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到GC Roots没有任何的引用链相连时(从GC Roots)到这个对象不可达)时,证明此对象不可用。
可作为GC Roots的对象:
虚拟机栈
1. 方法区的类属性所引用的对象
2. 方法区中常量所引用的对象
3. 本地方法栈中引用的对象
垃圾回收算法
标记清除算法
先标记出要回收的对象(一般使用可达性分析算法),再去清除,但会有效率问题和空间问题:标记的空间被清除后,会造成我的内存中出现越来越多的不连续空间,当要分配一个大对象的时候,在进行寻址的要花费很多时间,可能会再一次触发垃圾回收。
复制算法
堆:
- 新生代
Eden 伊甸园
Survivor 存活期
Tenured Gen 老年区 - 老年代
复制算法是将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,浪费较大。复制算法的执行过程如下图所示:
现在的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。
标记整理算法
对于老年代,回收的垃圾较少时,如果采用复制算法,则效率较低。标记整理算法的标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。很显然,整理这一下需要时间,所以与标记清除算法相比,这一步花费了不少时间,但从长远来看,这一步还是很有必要的。
分代收集算法
针对不同的年代进行不同算法的垃圾回收,针对新生代选择复制算法,对老年代选择标记整理算法。
垃圾收集器
Java的应用很广,内存区域也很多,可以使用不同的垃圾收集器。
Serial收集器
单线程垃圾收集器、最基本、发展最悠久。它的单线程的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。偶尔用在桌面应用中。
ParNew收集器
可多线程收集垃圾,收集新生代,使用收集算法
Parallel收集器
多线程收集垃圾,收集新生代,使用收集算法。Parallel收集器更关注系统的吞吐量,可以通过参数来打开自适应调节策略。
吞吐量:CPU用于运行用户代码的时间与CPU消耗的总时间的比值。
吞吐量 = (执行用户代码时间)/(执行用户代码时间+垃圾回收占用时间)
-XX:MaxGCPauseMillis 垃圾收集器最大停顿的时间,但最大停顿时间过短必然会导致新生代的内存大小变小,垃圾回收频率变高,效率可能降低。
-XX:CGTIMERatio 吞吐量大小(0-100),默认为99。
CMS收集器
Concurrent Mark Sweep,采用标记-清除算法,用于老年代,常与ParNew协同工作。优点在于并发收集与低停顿。
注:并行是指同一时刻同时做多件事情,而并发是指同一时间间隔内做多件事情
-
工作过程
- 初始标记
标记老年代中所有的GC Roots对象和年轻代中活着的对象引用到的老年代的对象,时间短; - 并发标记
从“初始标记”阶段标记的对象开始找出所有存活的对象; - 重新标记
用来处理前一个阶段因为引用关系改变导致没有标记到的存活对象,时间短; - 并发清理
清除那些没有标记的对象并且回收空间。
缺点:占用大量的cpu资源、无法处理浮点垃圾、出现Concurrent MarkFailure、空间碎片。
G1收集器
G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一,早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术。
优势:并行(多核CPU)与并发;
分代收集(新生代和老年代区分不明显);
空间整合;
限制收集范围,可预测的停顿。
步骤:初始标记、并发标记、最终标记和筛选回收。
新生代和老年代
一般新生成的对象都出现在Eden区,当Eden区被填满时,所有经过垃圾回收还存活的对象被复制到两个Survivor区域中的一个,我们假定是From区(两个区域实际上没有任何区别,From和To只是为了更好的说明工作流程),当From区域也被填满时,这个区域经过垃圾回收仍存活的对象将会被复制进入To区域,原From区域被清空,并且从Eden区过来的数据将直接进入To区域。当To区域也被填满时,之前从From区域过来的那部分数据如果仍在活动,将会被放到老年代。需要注意的是,两个Survivor区域总有一个会是空的。
通过年龄计数器。对象每经过了一个GC仍然存活,年龄计数器加一。当年龄超过了设定的值;则将其通过担保机制转移到老年代。或者动态判定,当survivor中年龄相同的多个对象的总和超过了survivor的一半,则将年龄大于等于该年龄的对象转移到老年代,无需等待设置的最大年龄值。年龄大的对象直接进入老年代。
- 老年代的对象:
1.大对象(字符串与数组),即超过了设定的值的对象,直接在老年代中分配;
2.长期存活的对象进入老年代
老生代内存满了之后,将触发 Full GC,针对整个堆(包括新生代、老年代)进行垃圾回收。