分代回收机制
JVM分代回收策略
JVM分代回收策略就是Java 虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代、老年代,永久代,不过永久代在JDK1.8永久移除了,被元空间取代了
新生代
新生代主要是用来存放新生的对象。一般占据堆空间的1/3,由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。一般采用的 GC 回收算法是复制算法。 新生代又可以继续细分为 3 部分:Eden、Survivor0(简称 S0)、Survivor1(简称S1)。这 3 部分按照 8:1:1 的比例来划分新生代。当JVM无法为新建对象分配内存空间的时候(Eden区满的时候),JVM触发MinorGc。因此新生代空间占用越低,MinorGc越频繁。MinorGC 触发机制是Eden区满的时候,JVM会触发MinorGC。
详细过程
- 绝大多数刚刚被创建的对象会存放在 Eden 区(如果新创建的对象占用内存很大则直接分配给老年代)
- 当 Eden 区第一次满的时候,当Eden区内存不够的时候就会触发一次Minor GC进行一次垃圾回收。首先将 Eden 区的垃圾对象回收清除,并将存活的对象复制到 S0,此时 S1 是空的。
- 下一次 Eden 区满时,再执行一次垃圾回收。此次会将 Eden 和 S0 区中所有垃圾对象清除,并将存活对象复制到 S1,此时 S0 变为空。
- 再下一次 Eden 区满时,再执行一次垃圾回收。此次会将 Eden 和 S1区中所有垃圾对象清除,并将存活对象复制到 S0,此时 S1 变为空。
- 如此反复在 S0 和 S1 之间切换几次(默认 15 次)之后,如果还有存活对象。说明这些对象的生命周期较长,则将它们转移到老年代中。
骚戴扩展:虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁, 当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold (阈值)来设置。
老年代
一个对象如果在新生代存活了足够长的时间而没有被清理掉,则会被复制到老年代。老年代的内存大小一般比新生代大,能存放更多的对象。如果对象比较大(比如长字符串或者大数组),并且新生代的剩余空间不足,则这个大对象会直接被分配到老年代上。
我们可以使用 -XX:PretenureSizeThreshold 来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。老年代因为对象的生命周期较长,不需要过多的复制操作,所以一般采用标记-整理算法或标记-清除的回收算法。老年代的对象比较稳定,所以MajorGC不会频繁执行。MajorGC的耗时比较长,因为要先整体扫描再回收,MajorGC会产生内存碎片。为了减少内存损耗,一般需要合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM。
永久代
永久代(Permanent Generation)是Java虚拟机中的一块内存区域,用于存储类、方法、常量等信息。在Java 8及以前的版本中,永久代是堆内存的一部分,它有一个固定的大小,一旦被占满,就会导致OutOfMemoryError异常。
永久代中存储的信息包括:
- 类的元数据信息,如类名、访问修饰符、字段、方法等。
- 字符串常量池。
- 静态变量。
由于永久代的大小是固定的,而且存储的信息会随着应用程序的运行而不断增加,因此容易导致OutOfMemoryError异常。为了解决这个问题,Java 8及以后的版本中,永久代被移除,取而代之的是元空间(Metaspace)。
元空间是Java虚拟机中的一块内存区域,用于存储类、方法、常量等信息。与永久代不同的是,元空间的大小不再是固定的,它可以根据需要动态调整大小。此外,元空间还可以使用本地内存,从而减少堆内存的使用。
元空间中存储的信息与永久代类似,包括类的元数据信息、字符串常量池、静态变量等。但是,元空间中的类元数据信息不再存储在虚拟机的堆内存中,而是存储在本地内存中。这样可以避免堆内存的碎片问题,提高应用程序的性能。
骚戴理解:方法区包括永久代(Permanent Generation),用于存储类的元数据信息、静态变量、常量等数据。在Java 8及以前的版本中,永久代是方法区的一部分,它有一个固定的大小,一旦被占满,就会导致OutOfMemoryError异常。在Java 8及以后的版本中,方法区被移除,取而代之的是元空间(Metaspace)