JVM 堆内存结构
堆内存的布局与垃圾回收器有关。
传统的垃圾回收器会把堆内存划分为:老年代和年轻代,年轻代又分为
- 伊甸园 Eden
- 幸存区 S0,S1
如果是 G1 垃圾回收器,会把内存划分为一个个的 Region,每个 Region 都可以充当
- 伊甸园
- 幸存区
- 老年代
- 巨型对象区
垃圾回收算法
记忆三种:
- 标记-清除算法。优点是回收速度快,但会产生内存碎片
- 标记-整理算法。相对清除算法,不会有内存碎片,当然速度会慢一些
- 标记-复制算法。将内存划分为大小相等的两个区域 S0 和 S1
- S0 的职责用来存储对象,S1 始终保持空闲
- 垃圾回收时,只需要扫描 S0 的存活对象,把它们复制到 S1 区域,然后把 S0 整个清空,最后二者互换职责即可
- 不会有内存碎片,特别适合存活对象很少时(因为此时复制工作少)
伊甸园、幸存区、老年代细节
- 对象最初都诞生在伊甸园,这些对象通常寿命都很短,在伊甸园空间不足,会触发年轻代回收,还活着的对象进入幸存区 S0,年轻代回收适合采用标记-复制算法
- 接下来再触发年轻代回收时,会将伊甸园和 S0 仍活着的对象复制到 S1,清空 S0,交换 S0 和 S1 职责
- 经过多次回收仍不死的对象,会晋升至老年代,老年代适合放那些长时间存活的对象
- 老年代回收如果满了,会触发老年代垃圾回收,会采用标记-整理或标记-清除算法。老年代回收时的暂停时间通常比年轻代回收更长
晋升条件
- 注意不同垃圾回收器,晋升条件不一样
- 在 parallel 里,经历 15 次(默认值)新生代回收不死的对象,会晋升
- 可以通过 -XX:MaxTenuringThreshold 来调整
- 例外:如果幸存区中的某个年龄对象空间占比已经超过 50%,那么大于等于这个年龄的对象会提前晋升
大对象的处理
- 首先大对象不适合存储在年轻代,因为年轻代是复制算法,对象移动成本高
- 注意不同垃圾回收器,大对象处理方式也不一样
- 在 serial 和 cms 里,如果对象大小超过阈值,会直接把大对象晋升到老年代
- 这个阈值通过 -XX:PretenureSizeThreshold 来设置
- 在 g1 里,如果对象被认定为巨型对象(对象大小超过了 region 的一半),会存储在巨型对象区
- Region 大小是堆内存总大小 / 2048(必须取整为2的幂),或者通过 -XX:G1HeapRegionSize 来设置