堆空间-内存分配

内存分配

:::info

前提:这里对象的分配策略是建立在堆空间中的,不考虑逃逸分析这种例外情况

核心:解决对象如何在堆空间分配的问题

:::

分区概述

  • 伊甸园区:

    • [几乎所有对象都是在伊甸园区中被创建的,并且在伊甸园区中被销毁]{.red}
    • [伊甸园区无法存放对象时将会触发 Minor GC]{.red}
  • 幸存者0区/幸存者1区:

    • 开始:[每次仅使用一个分区存放对象,另外一个分区暂时为空]{.red}

      :::info

      ① 存放对象的分区也称为 From 区,不存放对象的分区也称为 To 区

      ② 第一次触发 Minor GC 时将会任意选择一个幸存者分区存放对象

      :::

    • 过程:

      • [幸存者区无法触发 Minor GC,只有伊甸园区可以触发]{.red}
      • [幸存者区虽然无法触发 Minor GC,但是 Minor GC 依然会回收幸存者区的对象]{.red}
      • [触发垃圾回收机制后,存放对象的分区将会把所有对象复制到空的分区中,并且清除自己拥有的对象]{.red}
    • 特点:[幸存者区中的每个对象都拥有年龄计数器,用于判断是否进入老年代]{.red}

      幸存者区
  • 老年代:[老年代中的对象很少被垃圾回收机制回收]{.red}

分配策略

  • 对象优先在伊甸园区中分配
  • 大对象直接进入老年代
    • 内容:超过对象大小阈值的对象将会直接进入老年代
    • 设置阈值命令:[-XX:PretenureSizeThreshold=Size]
  • 长期存活的对象进入老年代
    • 内容
      • 每个幸存者区中的对象都具有年龄计数器
      • 每次从一个幸存者区移动到另一个幸存者区时,对象的年龄计数器就会加1
      • 对象的年龄计数器达到阈值之后,对象就会从幸存者区进入老年代区
    • 细节:
      • 默认阈值为 15
      • 设置阈值命令:[-XX:MaxTenuringThreshold=age]{.blue}
  • 动态对象年龄判断进入老年代
    • 内容:
      • 只要保证幸存者区中具有 [小于等于某年龄的对象的总大小]{.red} 超过幸存者区大小的一半
      • 那么 [大于等于该年龄的对象]{.red} 将会直接进入老年代,不需要等待年龄超过阈值
    • 细节:这个策略基本不怎么使用
  • 空间分配担保
    • 核心:[确保老年代能够容纳在幸存者区无法容纳的对象]{.red}
    • 内容:
      • [判断老年代最大连续可用空间是否大于新生代所有对象的总和]{.red}
        • 如果满足条件,那么这次 Minor GC 就是安全的
        • 如果不满足条件,那么将会继续执行另一个判断
      • [再次判断老年代最大可用空间是否大于历次晋升到老年代对象的平均大小]{.red}
        • 如果满足条件,仍然可以 Minor GC,但是是有风险的
        • 如果不满足条件,将会执行 Full GC,用户线程停止时间将会变长

分配过程

对象分配示意图

分配过程

对象分配过程

  • 开始创建对象

  • [采用大对象直接进入老年代的策略]{.red}

    • [如果超过对象的阈值:对象将直接进入老年代]{.blue}
    • 如果没有超过对象的阈值:接着采用其他策略分配
  • [判断伊甸园是否具有足够的空间容纳新对象]{.red}

    • [如果伊甸园没有足够的空间容纳新对象:触发 Minor GC / YGC]{.blue}

      :::info

      注:触发 Minor GC 之前需要执行空间分配担保

      :::

    • 如果伊甸园区可以容纳新对象:直接分配内存即可

  • [Minor GC / YGC 开始回收伊甸园区和幸存者区中的对象]{.red}

    • 被垃圾回收机制回收的对象将会被销毁

    • [判断幸存者区中对象的年龄是否达到阈值]{.blue}

      • [如果幸存者区中的对象达到阈值:晋升到老年代中]{.blue}
      • [如果幸存者区中的对象没有达到阈值:进入另一个空幸存者区]{.blue}
    • [判断幸存者区是否还有足够空间容纳从伊甸园区进入幸存者区的对象]{.red}

      • [如果幸存者区没有足够的空间容纳进入的对象:无法被容纳的对象直接晋升为老年代]{.blue}
      • [如果空幸存者区有足够的空间容纳所有对象:伊甸园和存放对象的幸存者区中没有被回收的对象将进入空幸存者区]{.blue}

      :::info

      ① 因为此前执行了空间分配担保,所以可以直接进入老年代,没有必要判断

      ② 伊甸园区无法被幸存者区容纳的对象进入老年代,幸存者区本身的对象是根据阈值来决定的

      ③ 判断阈值和年龄关系的同时也会采用 [动态对象年龄]{.red} 的策略进行优化

      :::

    • [进入空幸存者区的对象的年龄计数器增加1]{.red}

      :::info

      ① 第一次触发 Minor GC时,两个幸存者区都是空的,虚拟机自行选择一个即可

      ② 对象移动之后年龄计数器达到阈值的话,需要等待下一次移动才会进入老年代

      ③ 年龄计数器的默认阈值是 15

      :::

  • 再次判断伊甸园区是否具有足够的空间容纳新对象

    • 如果伊甸园区可以容纳新对象:直接分配内存即可
    • 如果伊甸园没有足够的空间容纳新对象:判断是否可以直接进入老年代
  • [判断老年代是否具有足够的空间容纳新对象]{.red}

    • [如果老年代没有足够的空间容纳新对象:触发 Major GC]{.blue}
    • [如果老年代有足够的空间容纳新对象:对象直接晋升到老年代]{.blue}
  • Major GC 开始回收老年代中的对象

  • 再次判断老年代是否具有足够的空间容纳新对象

    • 如果伊甸园区可以容纳新对象:直接分配内存即可
    • [如果伊甸园没有足够的空间容纳新对象:抛出 OutOfMemoryError 异常]{.blue}
Author: Fuyusakaiori
Link: http://example.com/2021/09/23/jvm/runtime/heapspace/内存分配/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.