常见的垃圾回收器
1.串行回收器(Serial Collection)
串行回收器通过运行Java程序时使用-XX+UseSerialGC附加选项启用。
串行回收器对Young代和Old代的回收都是串行的(只使用一个CPU),而且垃圾回收执行期间会使得应用程序暂停,具体策略为:Young代采用串行复制算法,Old代采用串行标记压缩算法。
上图为Young代的串行回收及回收完成后的示意图
如上,系统将Eden区中的活动对象直接复制到初始为空的Survivor区中(也就是To区),如果有些对象占用空间特别大,垃圾回收器会直接将其复制到Old代中。
对于From 区中的活动对象(至少经历过一次回收),到底是复制到To区中还是Old代中,则取决于这个对象的生存时间:如果这个对象的生存时间较长,它将被复制到Old代中,否则到To区中。
完成上述操作后,Eden和From中只剩下不可达对象,系统一次性回收其所有内存。此时,From区和To区发生了互换。
2.并行回收器(Parallel Collector)
并行回收器通过运行Java程序时使用-XX:+UseParallelGC附加项启用,它可以充分利用计算机的多个CPU来提高垃圾回收吞吐量。
并行回收器对于Young代采用与串行回收器基本相似的回收算法,只是增加了多个CPU并行的能力,即同时启动多线程并行执行;垃圾回收。线程数默认为CPU个数,当计算机中的CPU很多时,可以用-XX:ParallelGCThreads=size来减少并行线程的个数。
并行回收器对于Old代采用与串行回收器完全相同的回收算法,不管计算机有几个CPU,并行回收器依然采用单线程、标记整理的方式进行回收。
对于并行回收器而言,只有多CPU并行的机器才能发挥其优势(如果多线程共用一个CPU来回收垃圾,那么并不能提高效率)
3.并行压缩回收器(Parallel Compacting Collector)
并行压缩回收器是从JDK5 update 6开始引入的,它和并行回收器最大的不同是对Old代的回收使用了不同的算法
- 年轻代的回收算法(Minor Collection)
与并行回收器(Parallel Collector)相同 - 年老代的回收算法(Full Collection)
年老代分为三个步骤,标记、统计、合并。这里用到分的思想,把年老代划分为很多个固定大小的区(region)。 标记阶段,把所有存活的对象划分为N组(应该与回收线程数相同),每一个线程独立的负责自己那一组,标记存活对象的位置以及 所在区(Region)的存活率信息,标记为并行的。统计阶段,统计每一个区(Region)的存活率,原则上靠前面的存活率较高,从前到后, 找到值得合并的开始位置(绝大多数对象都存活的区不值得合并),统计阶段是串行的(单线程)。合并阶段,依据统计阶段的信息,多线程 并行的把存活的对象从一个区(Region)复制到另外一个区(Region)。
4.并发标识-清理回收器(CMS)
并发标识-清理回收器通过运行Java程序时使用-XX:+UseConcMarkSweepGc附加选项启动。
- 年轻代的回收算法(Minor Collection)
与并行回收器(Parallel Collector)相同 - 年老代的回收算法(Full Collection)
分为四个步骤,初始标记(Initial Mark)、并发标记(Concurrent Mark)、再次标记(Remark)、以及并发清理(Concurrent Sweep)。特别注意,没有合并操作,所以会有碎片。 - 初始化阶段: 暂停应用线程,找出所有存活的对象,耗时比较短,回收器使用单线程。
- 并发标记阶段: 回收器标记操作与应用并发运行,回收器使用单线程标记存活对象。
- 再次标记:并发标记阶段由于应用程序也在运行,这个过程中可能新增或者修改对象。所以再次暂停应用线程,找出所有修改的对象,使用多线程标记。
- 并发清理:回收器清理操作与应用并发运行,回收器使用单线程清理死亡对象。
对于串行、标记压缩回收器而言,它可以等到Old代满了以后再开始回收,反正垃圾回收总会让程序暂停。但CMS回收器要与应用程序并发执行,如果Old代满了才开始回收,那么应用程序将无内存可用,所以系统默认在Old代68%满的时候就开始回收。
而且CMS不会进行内存压缩,也就是说,不可达对象占用的内存被回收以后,垃圾回收器不会移动可达对象占用的内存,如上图。