一. 垃圾收集算法
1. 标记-清除算法
缺点:1.效率低,标记和清除两个过程的效率都不高;
2.空间问题,标记清除后会产生大量的不连续内存碎片。
2. 复制算法
将内存分成三块:一块较大的Eden和两块较小的Survivor空间。每次使用Eden和其中一块Survivor,回收后将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor空间上。如果复制过程中Survivor的空间不够用时,需要依赖老年代进行分配担保。
3. 标记-整理算法
和标记-清理算法类似,只不过后续步骤不是直接对可回收对象进行清理,而是让所有存货对象都向一边移动,然后直接清理掉端边界以外的内存,这样就保证了没有内存碎片的存在。这种方法比较适合老年代使用。
二. HotSpot的算法实现
1. 枚举根节点
准确式GC(即虚拟机知道哪块内存存放对象的具体类型),为了避免一个不漏的检查完所有执行上下文和全局的引用位置,虚拟机通过一组称为OopMap解决了分辨获取对象类型信息的问题。
2. 安全点
能够改变对象引用的指令非常多,如果针对每一条指令都生成对象的OopMap将会耗费大量的额外空间。为了解决这个问题,虚拟机只在特点的地方——一些“具有让程序长时间执行”的地方(为了将GC控制在一个合适的频率)如方法调用、循环跳转、异常跳转——这些地方被称为Safepoint。
为了同步所有线程,让他们都到达Safepoint之后再统一GC,有两种发放可供选择:
- 抢先式中断,先把所有线程全部中断,不再安全点上就恢复线程,让它跑到安全点上。
- 主动式,GC需要中断时设置一个标志,各线程会去主动轮询这个标志,当发现中断标志就自己中断挂起,轮询标志的地方和安全点是重合的。
疑问:为什么不是每次到达安全点再轮询?然后抢先中断方法的恢复线程后让它跑到安全点的过程是否和主动式有些类似??
3. 安全区域
在程序不执行即处于sleep状态或者blocked状态时无法响应JVM的中断请求、“走”到安全点挂起。这时候就需要安全区域(Safe Region)来解决。
Safe Region是指在一段代码片段中引用关系都不会发生变化,在这个区域中的任意地方开始GC都是安全的。在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那么当这段时间JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者整个GC过程),如果完成了,那线程就继续执行,否则它就等到直到收到可以安全离开Safe Region的信号为止。
疑问:这个真的解决了线程阻塞而无法响应JVM中断请求的问题吗?
三. 垃圾收集器
1. Serial(new:Serial——old:CMS、Serial Old)
单线程收集器,进行垃圾收集时必须暂停其他所有的工作线程直到它收集结束。
2. ParNew(new:ParNew——old:CMS、Serial Old)
Serial收集器的多线程版本,能与CMS配合工作是选择ParNew的一个重要原因。-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
3. Parallel Scavenge(new:Parallel Scavenge——old:Serial Old、Parallel Old)
CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控的吞吐量(Throughput)。
停顿时间越短就越适合需要与用户交互的程序,相应更快;高吞吐量则可以高效率的利用CPU时间,尽快完成程序的计算任务。
4. Serial Old(new:Serial、ParNew、Parallel Scavenge——old:Serial Old)
单线程收集器。
5. Parallel Old(new:Parallel Scavenge——old:Parallel Old)
在主要吞吐量以及CPU资源敏感的场合,可以优先考虑Parallel Scavenge加Parallel Old收集器组合。
6. CMS(new:Serial、ParNew——old:CMS、Serial Old)
- 初始标记(stop the world)
- 并发标记
- 重新标记(stop the world)
- 并发清理
缺点:
-
-
- 对CPU资源敏感。在并发阶段,虽然它不会导致用户线程停顿,但是会因为占用了一部分线程(CPU资源)而导致应用程序变慢。
- CMS无法处理浮动垃圾(Floating Garbage)。因为CMS并发清理阶段用户线程还在运行,所以伴随着程序运行还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后而只能留待下一次GC时再处理。这部分垃圾就称为浮动垃圾。另外一个相应的后果时CMS不能像其他线程一样等到老年代几乎被完全填满了在进行收集,因为它必须留一部分给垃圾收集时程序运行使用,所以CMS有一个老年代阀值,超过这个阀值就进行收集。
- CMS是一个基于“标记-清理”算法实现的收集器,会产生内存碎片
-
7. G1
- 并发与并行
- 分代收集
- 空间整合:基于“标记-清理”,没有内存碎片问题
- 可预测的停顿:消耗在垃圾收集上的时间不得超过N毫秒
使用G1收集器,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的,他们都是一部分Region(不需要连续)的集合。
G1可预测的停顿实现归功于避免进行全区域的垃圾收集。G1每次只收集回报最高的Region。
Region之间的对象引用虚拟机都是使用Rememberd Set来避免全堆扫描的。