jvm内存模型

JVM堆内存模型在1.7与1.8中有着较大的区别,需要分别说明。

JDK1.7 JVM堆内存模型

在Java1.7中,根据内存中对象按照生命周期长短进行划分,将内存区域拆分为三个generation(代):

  • 新生代(Young):生命周期比较短的对象,比如局部变量

  • 老年代(Tenured [ten nie de]):生命周期比较长的对象,比如ApplicationContextSessionFactory

  • 永久代(Perm [po m]):基本不会死亡的对象,比如加载的class信息

在JVM中,GC采用了分代算法,根据不同的代,来使用不同的回收算法

  • 新生代中存活对象较少,通常采用复制算法

  • 老年代中存活对象较多,通常采用标记清理法,或者标记整理法

每个不同的代,需要使用不同的GC算法,所以可以将GC分为以下几类:

  • Minor GC:负责年轻代的内存回收,采用复制算法

  • Major GC:负责老年代的内存回收,采用标记清除法

  • Full GC:清理整个堆空间—包括年轻代和老年代

年轻代Young中,又分为三个区域:

  • Eden区(一蹬):Java新对象的出生地

    • 如果新创建的对象占用内存很大,则直接分配到老年代

    • 当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收

  • ServivorFrom区:上一次GC的幸存者,作为这一次GC的被扫描者

  • ServivorTo区:保留了一次MinorGC过程中的幸存者

Minor GC的回收流程

  • Minor GC 采用复制算法

  • 每当对象熬过一次GC扫描,它的年龄就会+1

  • 先扫描EdenSurvivorFrom中的所有对象,找出存活的对象,并将其放入到SurvivorTo区域

  • 同时将这些对象的年龄+1

  • 如果年龄达到了老年代的要求( 默认是 15 岁,可以通过参数-XX:MaxTenuringThreshold 来设定),便将其移动到老年代中,如果老年代已经满了,就放入永久代

  • 然后清空 EdenSurvivorFrom区域

  • 然后将SurvivorFromServivorTo的角色互换,这样,下次仍然还会扫描SurvivorFromEden区域

Major GC的回收流程

  • Major GC采用标记清除算法

  • 首先扫描以一次老年代,并标记处没有存活的对象,并回收没有标记的对象

  • 因为需要全扫描,所以耗时很长

  • Major GC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配(内存整理)

永久代没有垃圾回收:

  • 内存的永久保存区域,主要存放Class和Meta(元数据)的信息

  • GC不会在主程序运行期对永久区域进行清理

  • 永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常

  • jdk1.8中已经移除了永久代,采用了元空间

JDK1.8 JVM堆内存模型

jdk1.8中移除了永久代,并使用元空间(Metaspace)替代,永久代需要被舍弃可以总结为以下几个

  • 因为永久代不会执行GC,而内存空间总是有限制,所以永久代始终存在OOM问题

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

  • 永久代会为 GC 带来不必要的复杂度

  • Oracle 可能会将HotSpotJRockit 合二为一

元空间:

  • 元空间不再存放在堆内存中,也不是虚拟机内存中,而是放到本地内存的空间中,元数据的大小限制,只会跟物理机的内存大小有关,从而解决了OOM的问题。

参考

最后更新于