简介

        本文介绍Java各个代的关系(内存模型)及垃圾收集流程。

内存模型

JDK8的内存模型

        在Java中所有的垃圾收集问题几乎都是针对堆内存空间完成的,但是要想充分理解垃圾的收集流程,必须首先掌握Java堆内存的最初内存模型组成。如图1所示:


JVM--Java垃圾收集--内存模型/垃圾收集流程_垃圾收集

图1:JDK8的内存模型​​​

内存模型的变化

        JDK1.8以前提供用永久代,而从JDK1.8后永久代被替换为元空间(MetaSpace)。在JDK1.8之前,HotSpot都在努力改变永久代的存储位置,例如,在JDK1.6时提供有永久代,到了JDK1.7时又将永久代的部分操作移交给了堆内存,而在JDK1.8时使用元空间代替了永久代。

        JDK 1.8之前的内存模型如图2所示。


JVM--Java垃圾收集--内存模型/垃圾收集流程_内存模型_02

图2:JDK8之前的内存模型

        可以发现,在JDK1.8之前都会提供有永久代,此部分内存是不受GC控制的。在最初的设计中,都将方法区保存在了永久代,所以一旦方法执行中出现了内存不足的情况,将会抛出:“OutOfMemoryError:PermGen space”错误。同时Oracle也在考虑将HotSpot与JRockit(此虚拟机不存在永久代)两个虚拟机合二为一,所以此内存空间被元空间所替代。

垃圾收集流程

         在整个Java內存模型中,主要有3块内存区:年轻代(Young)、老年代(Tenured)、元空间(MetaSpace),同时还会有几块动态调整的内存伸缩区(当几个内存区空间不足时动态扩充)。而JVM的内存回收就是对这几块空间的回收处理操作,对于内存分配与GC的执行流程如图3所示。


JVM--Java垃圾收集--内存模型/垃圾收集流程_内存空间_03

图3:JVM垃圾收集流程

上图中的垃圾回收主要是针对年轻代(Eden+Survivor)与老年代(Tenured)完成的。

具体流程如下:


  1.  当使用关键字new创建一个新对象时,JVM会将新对象保存在Eden区,此时需要判断Eden区是否有空余空间。

  1. 如果没有,则会执行“MinorGC(年轻代GC)”。
  2. 如果有,则直接将新对象保存在Edeti区之内;

  1. 执行完“MinorGC”后会清除不活跃的对象,从而释放Eden区的内存空间,随后对Eden空间再次判断。

  1. 如果此时Eden区的空间依然不足,则会将部分活跃对象保存在Survivor区。
  2. 如果此时剩余空间可以直接容纳新对象,则会直接为新对象申请内存空间;

  1. 由于Survivor区也有对象会存储在内,所以在保存Eden区发送来的对象前首先需要判断其空间是否充足。

  1. 如果Suivivor有足够的空余空间,则直接保存Eden区晋升的对象。此时Eden区空间得到释放,随后可以在Eden区为新的对象申请内存空间。
  2. 如果Survivor区空间不足,则需要将Survivor区的部分活跃对象保存到Tenured区。

  1. Tenured区如果有足够的内存空间,则会将Survivor区发送来的对象进行保存。

  1. 如果此时Tenured区的内存空间也已经满了,则将执行“FullGC”(完全GC或称为MajorGC,其包括年轻代和老年代,相当于使用“Runtime.getRuntime().gc()”处理)以释放年轻代和老年代中保存的不活跃对象。
  2. 如果在释放后有足够的内存空间,Survivor将保存Eden发送来的对象,这样就可以在Eden区内有足够的内存保存新的对象。

  1. 此时,如果老年代的内存区也己经被占满,则会抛出“OutOfMemoryError(OOM错误)”,程序将中断运行。

        可见,Java在每次创建对象时如果发现内存不足都会自动向其他区域延伸。为了提高性能,在实际应用中可能会开辟尽量大的内存空间,以实现更加合理的GC控制。

其他网址

《Java开发实战经典》=> 23.3 JVM垃圾收集