首先我们需要知道Java的内存分配与回收全部由JVM垃圾回收机制自动完成。每种JVM实现可能采用不同的方法实现垃圾回收机制。在收购SUN之前,Oracle使用的是JRockit JVM,收购之后使用HotSpot JVM。目前Oracle拥有两种JVM实现并且一段时间后两个JVM实现会合二为一。HotSpot JVM是目前Oracle SE平台标准核心组件的一部分。市面上探讨垃圾回收机制,默认都是基于HotSpot JVM的。Ok,我们切入正题,先来看下JVM的内存区域模型,
这张图非常直观的展示了JVM的内存区域划分,我们关注的GC在哪里呢?答案在下面这张图里面。
对比两张图,我们发现,垃圾收集器跟jit编译器一起构成了JVM的执行引擎,gc的“势力范围”是哪里呢?且看下图
再次强调这是hotspot的堆模型,其他JVM(如JRockit、IBM J9等)是不存在永久代的概念的,有趣的是在Java8中,hotspot也将永久代取消了,改为了metaSpace,直接采用本地内存,这也减轻了堆的压力。
我们暂且看应用更为广泛的JDK8之前的堆分区:
1、新生代(Young Generation):绝大多数最新被创建的对象会被分配到这里,由于大部分对象在创建后会很快变得不可到达,所以很多对象被创建在新生代,然后消失。对象从这个区域消失的过程我们称之为”minor GC“。
Eden空间(Eden space,任何实例都通过Eden空间进入运行时内存区域)
S0 Survivor空间(S0 Survivor space,存在时间长的实例将会从Eden空间移动到S0 Survivor空间)
S1 Survivor空间 (存在时间更长的实例将会从S0 Survivor空间移动到S1 Survivor空间)
2、老年代(Old generation): 对象没有变得不可达,并且从新生代中存活下来,会被拷贝到这里。其所占用的空间要比新生代多。也正由于其相对较大的空间,发生在老年代上的GC要比新生代少得多。对象从老年代中消失的过程,我们称之为”major GC“(或者”full GC“)
S1中的实例,如果存活时间足够长将提升到老年代(Old Generation)。
3、永久代(Permanent Generation)包含类、方法等细节的元信息,也就是JVM内存结构中的方法区,他用来保存类常量以及字符串常量。因此,这个区域不是用来永久的存储那些从老年代存活下来的对象。这个区域也可能发生GC。并且发生在这个区域上的GC事件也会被算为major GC。hotspot由于把GC扩展到方法区,所以把方法区抽象成永久代,作为堆的逻辑结构。再补充一点,著名的字符串常量池在JDK7之前,就在永久代里面,JDK7之后从永久代中挪出到正经的堆中了。毕竟永久代实际上并不在堆里,只是hotspot为了扩张GC的势力范围,才有了上面的那张图。
下面我们来看看新生代的GC过程:
新生代是用来保存那些第一次被创建的对象,他可以被分为三个空间
一个伊甸园空间(Eden )
两个幸存者空间(Survivor )
一共有三个空间,其中包含两个幸存者空间。每个空间的执行顺序如下:
1、绝大多数刚刚被创建的对象会存放在伊甸园空间。
2、在伊甸园空间执行了第一次GC之后,存活的对象被移动到其中一个幸存者空间。
3、此后,在伊甸园空间执行GC之后,存活的对象会被堆积在同一个幸存者空间。
4、 当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。之后会清空已经饱和的那个幸存者空间。
5、在以上的步骤中重复几次依然存活的对象,就会被移动到老年代。
如果你仔细观察这些步骤就会发现,其中一个幸存者空间必须保持是空的。如果两个幸存者空间都有数据,或者两个空间都是空的,那一定标志着你的系统出现了某种错误。