一个应用启动,操做系统会给他分配一个初始的内存大小,由上可知,这部份内存大部分应该属于堆内存,JVM 为了更好地利用管理这部份内存,对该区域作了划分。一部分红为新生代,另外一部分称为老年代。算法
一开始对象的建立都发生在新生代,随着对象的不断建立,若是新生代没有空间建立新对象,将会发生 GC ,这时的 GC 称之为 Minor GC,位于新生代的对象每通过一次 Minor GC 后,若是这个对象没有被回收,则为本身的标记数加1,这个标记数用于标识这个对象经历了多少次的 Minor GC,对于 Sun 的 Hotspot 虚拟机,若是这个次数超过 15 ,该对象才会被移动到老年代。性能
随着时间的推移,若是老年代也没有足够的空间容纳对象,老年代也会试着发起 GC,这时的 GC 被称为 Full GC。spa
相比 Minor GC,Full GC 发生的次数比较少,可是每发生一次 Full GC,整个堆内存区域都须要执行一次垃圾回收,这对程序性能形成的影响比 Minor GC 大不少。因此咱们应该尽可能避免或者减小 Full GC 的发生。操作系统
同时,在堆内存区域,发生最多的 GC 情形就是新生代的 Minor GC 了,由于全部的对象会优先去新生代开辟空间,因此这块的内存变化会很快,只有内存不够用,就会发生 GC,可是通常的 Minor GC 执行比 Full GC 快不少。为何呢?由于新生代和老年代的垃圾回收算法不同。指针
这是最基础的收集算法,如它的名字同样,算法分为“标记”和“清除”两个阶段:code
首先标记出全部须要回收的对象,在标记完成后统一回收掉全部被标记的对象。对象
之因此说它是最基础的收集算法,是由于后续的收集算法都是基于这种思路并对其缺点进行改进而获得的。内存
它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另一个是空间问题,标记清除以后会产生大量不连续的内存碎片,空间碎片太多可能会致使,当程序在之后的运行过程当中须要分配较大对象时没法找到足够的连续内存而不得不提早触发另外一次垃圾收集动做。虚拟机
为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用过的内存空间一次清理掉。效率
这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂状况,只要移动堆顶指针,按顺序分配内存便可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,未免过高了一点。
可是这种算法的效率至关高,因此,如今的商业虚拟机都采用这种收集算法来回收新生代。为何新生代可使用复制算法呢?
IBM 有专门研究代表,新生代中的对象 98% 都是朝生夕死,因此就不须要按照1:1的比例来划份内存空间。这里鉴于此,新生代采用了以下的划分策略。
如今把新生代再划分为三部分,一块较大的 Eden(伊甸园) 和两块较小的 Survivor(幸存者) 区域。
当回收时,将 Eden 和 Survivor 中还存活着的对象一次性地拷贝到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor的空间。HotSpot 虚拟机默认Eden和Survivor的大小比例是8∶1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存是会被“浪费”的。
这样清理完成后,原来的 Survivor 就空了,并一直保持为空,直到下次 Minor GC 时,它再做为存活对象的盛放地。两个 Survivor 就这样轮流当作 GC 过程当中新生代存活对象的中转站。
可是,若是使用复制算法的内存区域有大量的存活对象时,复制算法就会变得捉襟见肘,这时须要更大的 Survivor 区用于盛放那些存活对象,甚至可能须要 1:1的比例。因此针对堆内存区域的老年代,就有了下面的算法。
标记过程仍然与“标记-清除”算法同样,但后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存
。这种方法避免了碎片的产生,同时也不须要一块额外的内存空间,对于老年代会比较合适。
可是相比复制算法,虽然该算法占用的内存空间少,可是耗费的垃圾回收时间会比复制算法久,因此上面也说了
咱们应该尽可能避免或者减小 Full GC 的发生。
这两种算法用精炼的语言描述就是
一句话 鱼与熊掌不可兼得,可是针对新生代和老年代,他们都是最佳的选择。