GC:JVM的新生代、老年代、MinorGC、MajorGC

    首先,看一张图,JVM堆内存的分布图如下:   

GC:JVM的新生代、老年代、MinorGC、MajorGC

    之前我们把JVM内存分为了堆,栈,方法区,直接内存等,而我们在开发过程中最关注的的就是堆区,同时这也是GC所活跃的位置,准确的来说上诉分类是GC做的分类。

      先说说新生代,新生代占三分之一大小,由于要频繁地创建对象,所以是MinorGC的活跃范围。新生代分为Eden区域,SurvivorFrom,SurvivorTo三个区域,翻译起来特别有意思,Eden(伊甸),SurvivorFrom(幸存者来自),SurvivorTo(幸存者到),如此我们就可以开始顾名思义了。

    先说说Eden,伊甸园是生命开始的地方,所以一个对象最开始被新建,便是被放在Eden区域(当新建的对象所需内存过大时,便会直接放入老年代),当Eden区域内存不足的时候便会进行一次MinorGC。

    一次MinorGC过后,未在不可达位置的对象,就成了幸存者。

    SurvivorTo区域用于存放一次MinorGC后的幸存者,SurvivorFrom区域用于上一次GC的幸存者,作为这一次MinorGC扫描的对象。

    好了,我们就可以捋一捋MinorGC的全过程了,MinorGC采用复制算法,最早会把Eden区域以及SurvivorFrom区域存活的对象挪至SurvivorTo区域,同时年龄+1(有的对象的年龄到了,就会被挪至老年区域,而SurvivorTo区域如果满了的话也会直接挪至老年区),然后清空Eden区域和SurvivorFrom区域,再最后,将SurvivorFrom和SurvivorTo的内容互换,SurvivorFrom就成了下一次要被MinorGC扫描的位置了。

    老年代是用作存放生命周期较长的对象的,所以MajorGC的运作不会太频繁,大多发生在MinorGC执行以后新生代有对象挪至老年代导致老年代内存不足才会执行MajorGC。当无法找到足够大的连续内存分配给新建的较大对象时,也会触发MajorGC。

    MajorGC采用标记—清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。

     当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。

     永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域. 它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理(存在疑惑,应该是类的所有实例化对象被回收时,且该类加载器也被回收时,最后类未在任何地方被引用,类就会被卸载)。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。

    在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。

    元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中. 这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制.

    采用元空间而不用永久代的几点原因:(参考:http://www.cnblogs.com/paddix/p/5309550.html)

  1、为了解决永久代的OOM问题,元数据和class对象存在永久代中,容易出现性能问题和内存溢出。


  2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出(因为堆空间有限,此消彼长)。


  3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。


  4、Oracle 可能会将HotSpot 与 JRockit 合二为一。