Java 垃圾回收机制之内存分配

Author: Mike Xie


内存分配

在JVM中,内存是按照分代进行组织的。

垃圾回收的时候,真没有必要扫描整个堆

image

此处非堆内存指代的是方法区

堆内存分为年轻代和年老代,非堆内存主要是Permanent区域,主要用于存储一些类的元数据,常量池等信息。

而年轻代又分为两种,一种是Eden区域,另外一种是两个大小对等的Survivor区域。之所以将Java内存按照分代进行组织

主要是基于

  1. 这样一个“弱假设-大多数对象都在年轻时候死亡。
  2. 只有很少的由老对象(创建时间较长的对象)指向新生对象的引用

同时,将内存按照分代进行组织,使得我们可以在不同的分代上使用不同的垃圾回收算法,使得整个内存的垃圾回收更加有效。

对象优先在 Eden 分配

新生代

绝大多数最新被创建的对象会被分配到这里,由于大部分对象在创建后会很快变得不可到达,所以很多对象被创建在新生代,然后消失。对象从这个区域消失的过程我们称之为”minor GC“。

大对象直接进入老年代

大对象

所谓大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组大对象对虚拟机的内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有不少内存空间时就不得不提前触发垃圾收集以获取足够的连续空间来“安置”它们。

老年代

对象没有变得不可达,并且从新生代中存活下来,会被拷贝到这里。其所占用的空间要比新生代多。也正由于其相对较大的空间,发生在老年代上的GC要比新生代少得多。对象从老年代中消失的过程,我们称之为”major GC“

长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄计数器,如果对象在Eden出生并经历过第一次Minor GC后仍然存活,并且被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄为1.对象在Survivor区中每熬过一次Minor GC,年龄增加一岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。

空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管是有风险的;如果小于或者设置值不允许担保失败,则需要改为进行一次Full GC。

垃圾回收

Minor GC

  1. 当Eden区满时,触发Minor GC
  2. 又称新生代GC,指发生在新生代的垃圾收集动作
  3. 因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快

Full GC

  1. 调用System.gc时,系统建议执行Full GC,但是不必然执行

  2. 老年代空间不足

  3. 方法去空间不足

  4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存

  5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

年轻代的垃圾回收

在年轻代上采用的垃圾回收算法是 “Mark-Copy” 算法,并不同于我们前面所了解的任何一种基本垃圾回收算法,但是Mark算法是一样的,基于根对象找到所有的可达对象,具体可看Mark-Sweep算法中的Mark步骤. 而对于Copy算法,它仅仅是简单的将符合一定年龄的对象从一个分代拷贝到另一个分代

具体的回收过程如下

image