JVM 堆内存模型与 GC 策略

Java 中堆内存是 JVM 管理内存中最大的一块内存,同时又是 GC 管理的重要区域。

Java 堆内存主要分成两个区域:

1,年轻代。年轻代内部又分成了两个区,一个是 Eden 区,一个是 Survivor 区。Survivor 区又划分成两块,一块是 from 区,一块是 to 区;

2,老年代。

具体一点可以看图:

一、年轻代

IBM 公司的专业研究表明,有将近 98% 的对象是朝生夕死,所以针对这一现状大多数情况下,对象会在新生代 Eden 中进行分配,当 Eden 区没有足够空间的时候,虚拟机会触发 Minor GC。

Minor GC 的回收速度很快。通过 Minor GC 后,Eden 区会被清空。Eden 区的绝大部分对象在这个时候都会被回收,剩下的那些无需被回收的对象会被放到 Survivor 的 from 区,如果 from 区放不下就直接会被放到 Old 区。

等到再次触发 Minor GC 后会将 Eden 区和 from 区存活的对象放到 to 区。同样的,如果 To 区放不下就会往 Old 区里放。等到再下次触发 Minor GC 后会将 Eden 区和 to 区存活的对象放到 From 区。Minor GC 会将年轻代的存活的数据在 from 区和 to 区来回存放。

在 Survivor 区中的存活数据,每经历一次 Minor GC ,这些对象的年龄就会加 1,当长期存活的对象年龄达到 15 岁的时候就会被移到老年代。当然这个 15 ,JVM 支持特殊设置。

另外还有一个机制,虚拟机并不一定要对象年龄到达 15 岁才会放入老年代,如果 Survivor 空间中相同年龄对象的大小的和大于 Survivor 空间的一半,年龄大约等于该年龄的对象就可以直接进入老年区,无需等待“成年”,这点类似于负载均衡。

二、老年代

老年代占据着 2/3 的堆内存空间,只有在 Major GC 的时候才会清理,每次 Major GC 都会触发 “Stop-The-World”。内存越大,STW 的时间就越长,所以内存也不是越大越好。

除了年轻代那里的数据会进入老年代之外,还有一种特殊情况:大对象。大对象是指大量连续内存空间的对象,这部分对象不管其生命周期有多短,都会直接进入老年代。这样做的目的是为了避免在 Eden 区和两个 Survivor 区之间发生大量的内存复制。所以一定要注意这些大对象。