Java的一个核心优势就是垃圾回收功能,每个C语言开发工程师的一个对象回收的烦恼。
垃圾回收的灵魂三问???
1)回收什么
2)何时回收
3)如何回收
1.回收什么
当一个对象不存在任何引用时(循环引用除外),此对象将失去存在的价值,这样的对象就可以被定义为垃圾对象,可回收对象。
1.1 回收方法
1)引用计数法(存在循环引用问题)
2)可达性分析算法
1.2 GcRoot根
与垃圾回收无关且存活对象。
虚拟机栈中局部变量表中引用对象。
本地方法栈中JNI引用的对象。
方法区中静态变量、常量引用的对象。
2.何时回收
当存储空间不足达到预设阈值后(新生代,老年代,方法区中空间不足或者达到预设阈值后触发GC),或者发起手动GC后(System.gc()不一定真的触发GC)。回收条件满足后也不是立刻马上进行回收,需要程序执行到安全点或者安全区后才会开始回收。
2.1 OopMap
回收时StopTheWord(STW)避免运行时数据的影响,同时又通过OopMap存储存放对象引用【类加载时生成】避免全局扫描查询引用对象的链关系。引用关系也是动态变化的所以并不是在任何情况下都会记录对象引用关系到OopMap。只有在特定的位置才会记录(安全点);
2.2 安全点(safe point)
进行GC时程序停顿(中断执行)的位置。存在两种停顿方式:抢先中断(GC时中断所有线程,每个线程自己判断是否在安全点上,不在恢复运行至安全点【无虚拟机在使用】),主动中断(GC设置标志,个线程主动轮休标志)。
2.3安全区(safe region)
范围更大的安全点【当线程没有在执行中[stop/block]】,安全区内线程在GC时可以继续执行,走出区域时需要判断GC是否执行完成,未完成情况需要停止执行。
3.如何回收
3.1 回收算法
1)复制算法 速度快,浪费空间
2)标记清除 速度快,空间碎片
3)标记整理 速度慢
3.2 并行、并发回收
并行:多线程回收,停止用户线程
并发:回收线程与用户线程同时执行
3.3 回收吞吐量
吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。
3.4 Minor GC 和 Full GC
新生代GC(Minor GC):发生在新生代的垃圾回收,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
老年代GC(Full GC / Major GC):发生在老年代的垃圾回收,出现了Full GC,经常会伴随至少一次的Minor GC(Parallel Scavenge收集器的收集策略里有可以直接进行Full GC的策略选择)。Full GC的速度一般会比Minor GC慢10倍以上。
4.回收器
4.1 Serial(串行 复制)
Serial是一个单线程新生代收集器,采用复制算法,Serial只会使用一个CPU或一条收集线程去完成垃圾收集工作,在进行垃圾收集时,必须暂停其他所有的工作线程(Stop The World),直到集结束为止。
可搭配Serial Old、CMS使用
4.2 ParNew(并行 复制)
ParNew就是Serial多线程版本,采用也是复制算法,用于新生代垃圾回收;ParNew在进行垃圾收集时,也必须暂停其他所有的工作线程(Stop The World),直到集结束为止。ParNew在单CPU的环境回收效率要低于Serial(需要考虑线程交互的开销)。
可搭配Serial Old、CMS使用
4.3 ParallelScavenge(并行 复制)
Parallel Scavenge是一个并行的多线程新生代收集器,采用也是复制算法,垃圾收集时也必须暂停其他所有的工作线程(Stop The World)。Parallel Scavenge的侧重点与其他收集器不同,其他收集器的侧重点都是是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge的目标是达到一个可控制的吞吐量(Throughput)。
低停顿高响应的收集器适合于用户交互的程(停顿时间短==响应速度快)。高吞吐的收集器更适合在后台运算而不需要太多交互的任务(高吞吞==高CPU利用率==快速完成计算)。
可搭配Serial Old、Parallel Old使用
4.4 SerialOld(串行 标记整理)
Serial Old 是一个单线程老年代收集器,采用标记整理算法,Serial Old 只会使用一个CPU或一条收集线程去完成垃圾收集工作,在进行垃圾收集时,必须暂停其他所有的工作线程(Stop The World),直到集结束为止。
可搭配Serial 、ParNew、Parallel Scavenge使用
4.5 Parallel Old(并行 标记整理)
Parallel Old是一个多线程并行的老代收集器,采用标记整理算法,垃圾收集时也必须暂停其他所有的工作线程(Stop The World)。与Parallel Scavenge一样都侧重于吞吐量。
可搭配Parallel Scavenge使用
4.6 CMS(并发 标记清除)
CMS(Concurrent Mark Sweep)是一个多线程并发的老代收集器,采用标记清除算法,在初始标记阶段(单线程 快速)与重新标记阶段(多线程并行 快速)都需要暂停其他所有的工作线程(Stop The World);在并发标记阶段与并发清理阶段都可以与用户线程并发执行(虽然两个阶段耗时都较长但是无效【Stop The World】)。
初始标记(initial mark):
仅标记GcRoot直接关联的对象(stop the word),耗时短
并发标记(concurrent mark):
根据初始标记结果进行链路追踪(GC Roots Tracing),耗时长
重新标记(remark):
修正并发标记过程中的变动(stop the word),耗时稍长
并发清理(concurrent sweep):
并发清理标记对象,耗时长
缺点:
1)CPU资源非常敏感
2)无法处理浮动垃圾【设计合理的回收触发阈值】
3)产生空间碎片【开启碎片整理】
4.7 G1(并发、并行 标记整理、复制)
G1(Garbage-First)是一个多线程可预测的停顿的收集器,适用于年轻代与老年代(G1将整个堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而是由一个个无需连续的Region组成的集合),整体采用标记整理算法,局部采用复制算法(两个Region之间采用复制算法,保证无空间碎片)。
初始标记(Initial Marking):
仅标记GcRoot直接关联的对象(stop the word),耗时短
并发标记(Concurrent Marking):
从GcRoot开始扫描对象,进行可达性分析,找到存活对象,耗时长,并发执行
最终标记(Final Marking):
修正并发标记过程中的变动(jvm将并发标记过程中变化记录在线程的Remembered Set Logs里面,最终标记阶段把Remembered Set Logs的数据合并到Remembered Set中),(stop the word),耗时稍长并行
筛选回收(Live Data Counting and Evacuation):
评估各个Region中的回收价值和成本,根据期望GC的停顿时间制定回收计划(根据期望GC的停顿时间可以只回收部分Region),并行赛选回收对象(stop the word),耗时长并行
收集器 | 串行 并行 并发 | 新生代 | 算法 | 目标 | 适用场景 |
Serial | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单CPU环境下的Client模式 |
Serial Old | 串行 | 老年代 | 标记整理 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
ParNew | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
Parallel Old | 并行 | 老年代 | 标记整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 并发 | 老年代 | 标记清除 | 响应速度优先 | 集中在互联网站或B/S系统服务端上的Java应用 |
G1 | 并发,并行 | all | 标记整理+复制算法 | 响应速度优先 | 面向服务端应用,将来替换CMS |
java7、java8 默认 Parallel Scavenge + Parallel Old, java9 默认G1