再介绍垃圾回收算法以前,先来看看 Java中的堆,Java里的堆指的是用于存放 Java 对象的内存区域。JVM的堆被同一个JVM实例中全部的Java线程共享,它一般由某种自动管理机制所管理,这种机制一般叫作"垃圾回收"。html
在Java 1.8 中,堆的内存模型大体以下: java
堆大小 = 新生代 + 老年代。其中堆的大小能够经过参数 -Xms,-Xmx
来指定。算法
默认的,新生代(Young) 与老年代(Old)的比例的值是 1:2 (该值能够经过参数 -XX: NewRatio来指定),即: 新生代(Young) = 1/3的堆空间大小,老年代(Old) = 2/3的堆空间大小。oracle
其中,新生代(Young)被细分为 Eden 和 两个 Survivor区域,这两个 Survivor区域分别被命名为 from 和 to,以示区分。jvm
默认的,Eden:from:to = 8:1:1 (能够经过参数 -XX: SurvivorRatio来设定),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。jsp
JVM每次只会使用 Eden和其中的一块 Survivor区域来为对象服务,因此不管何时,总有一块 Survivor区域是空闲着的,新生代实际可用的内存空间为 90% 的新生代空间。post
在GC算法中,最简单的就是 "标记-清除"(Mark-Sweep)算法。它的原理比较简单,首先根据可达性分析算法对不可达对象进行标记,在标记完成后统一回收全部被标记的对象。标记-清除算法的执行过程以下图: 网站
基于Mark-Sweep的GC 多用于老年代.net
复制算法的思路是它将可用内存按容量划分为大小相等的两块,每次只用其中的一块。当这块内存用完了,就将还存活的对象复制到另一块上面,而后再把已使用过的内存空间一次清理掉。线程
这样每次都是对半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂状况,只要移动堆顶指针,按顺序分配内存便可。可是这种算法是用空间换时间,代价是将内存缩小为原来的一半,代价很高。而新生代的对象通常是存活时间较短的对象,GC频率较高,占内存较少,所以新生代通常都采用基于复制的GC。复制算法过程以下:
HotSpot 虚拟机将新生代内存分为 一块较大的 Eden空间和两块较小的 Survivor空间,Eden和Survivor的大小比例是8:1。每次新生代中可用内存空间为整个新生代容量的 90%。咱们没有办法保证每次回收都只有很少于 10%的对象存活,当 Survvivor 空间不够用时,须要依赖老年代进行分配担保。
复制收集算法在对象存活率较高时就要进行较多的复制操做,效率会变低,它比较适合收集新生代对象,至于老年代这种通常不选用复制算法。根据老年代的特色,可使用 "标记-整理"算法或者"标记-清除"算法。
标记 - 整理算法能够解决内存碎片的问题,并且思路也比较简单,它的思想就是,让全部存活的对象都向一端移动,而后直接清理掉边界之外的内存,以下图所示:
当前商业虚拟机的垃圾收集都采用"分代收集",将堆分为新生代和老年代,根据各个年代的特色采用最适当的收集算法:
下面的分析参照 R大对于GC算法的分析:hllvm.group.iteye.com/group/topic…
分代式 GC里,老年代经常使用 mark-sweep
(标记 - 清除算法),或者是 mark-sweep /mark-compact 的混合方式,通常状况下用 mark-sweep,统计估算碎片量达到必定程度时用 mark-compact(标记 - 整理)。这是由于传统上你们认为老年代的对象可能会长时间存活且存活率高,或者是比较大,这样拷贝起来不划算,还不如采用就地收集的方式。 Mark-Sweep
,Mark-compact
,copying
这三种基本算法里,只有mark-sweep是不移动对象的(也就是不拷贝的),因此老年代常选用 mark-sweep。固然针对不一样的垃圾收集器,GC 算法是有区别的
如下是三种算法的比较:
mark-sweep | mark-compact | copying | |
---|---|---|---|
速度 | 中等 | 最慢 | 最快 |
空间开销 | 少(但会堆积碎片) | 少(不堆积碎片) | 一般须要活对象的2倍大小(不堆积碎片) |
移动对象? | 否 | 是 | 是 |
关于时间开销:
若是把 mark,sweep,compact,copying这几种动做的耗时放在一块儿看,大体有这样的关系:
compaction >= copying > marking > sweeping marking + sweeping > copying
总结一下:
在分代式假设中,年轻代中对象在 minor GC 时的存活率应该很低,这样用copying算法就是最合算的,由于其时间开销与活对象的大小成正比,若是没多少活对象,它就很是快。并且 young GC 自己应该比较小,就算须要2倍空间也只会浪费不太多的空间
而老年代被 GC 时对象存活率可能会很高,并且假定可用剩余空间不太多,这样copying 算法就不太合适,因而更可能选用另两种算法,特别是不用移动对象的 Mark-Sweep算法
不过 HotSpot VM中除了CMS收集器以外的其余收集器都是会移动对象的,也就是要么是 copying,要么是mark-compact的变种
-XX:+<option>
启用选项 例如:-XX:+PrintGCDetails启动打印GC信息的选项,其中+号表示true,开启的意思-XX:-<option>
不启用选项 ,例如:-XX:-PrintGCDetails关闭启动打印GC信息的选项,其中-号表示false,关闭的意思-XX:<option>=<number>
-XX:<option>=<string>
更多JVM参数选项设置,请参考Oracle官方网站给出的相关信息: www.oracle.com/technetwork…
以上主要参考了《深刻理解Java虚拟机》这本书以及R大对于GC算法的分析,本人对于JVM是渣渣级选手,若有问题之处,欢迎指出
另外关于垃圾算法更加详解的解释,三种算法的具体实现参考 中村成洋的《垃圾回收的算法与实现》