垃圾回收算法
JVM中常用的垃圾回收算法主要有标记清除算法,复制算法,标记整理算法和分代收集算法。
标记清除算法
首先就是遍历GC Roots将没有关联的对象进行标记,然后将这些标记的对象清除掉。
这个算法的优点是简单,但是效率低,而且清理后的内存会不相连,产生大量的内存碎片。
复制算法
就是将内存分为大小相等的两个部分,让创建的对象在其中的一个上面进行存储,当存储满了或者空间不够,就将空间进行一次垃圾回收,将存活下来的对象放到另一个内存上,把用过的这个清除掉。
优点就是解决了内存碎片的问题,但是代价很大,因为很多对象都是朝生夕死的,所以浪费50%的空间代价很大。另外对象存活率高时会频繁进行复制。
因此后来对复制算法进行了改进,因为很多新对象是朝生夕死的,所以将内存分为一个Eden区和两个survivor区,占比分别是8:1:1,将新产生的对象放在Eden区,等到Eden区存满的时候触发一次minor GC,将存活下来放到另一个survivor里,然后清除掉用过的Eden区和survivor区。
当然,也可能会出现剩余10%的Survivor空间不够复制原有存活对象的情况,那就需要依赖其它内存(这里指老年代)进行分配担保。通过分配担保机制,这些对象会直接进入老年代。
https://blog.csdn.net/VIP099/article/details/108567125
标记整理算法
分为两个阶段标记和整理:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
第二阶段是整理:就是移动所有存活的对象,且按照内存地址次序依次排列,然后将空闲的内存全部回收。
分代收集算法
根据对象的存活周期将内存划分为几块。一般包括年轻代、老年代 和 永久代,新生代主要选择复制算法,老年代主要选择标记清除或标记整理算法。
新生代
年轻代回收频率高,为了进一步缩小回收范围,将年轻代又分为:
- Eden:新生对象首先会分配到这里
- Survivor:初生区回收后存活对象转移到幸存区,S0与S1使用时每次只用其中一块,回收时交换。
- S0(From Survior):发生回收的区域,存活对象复制到S1,下次运行时暂不使用
- S1(To Survior):接收S0的存活对象,作为下次运行时使用的区域
当年轻代发生GC后内存不足,则将Survivor区存活的对象按一定规则进入老年代,由老年代作年轻代的后备。年轻代内存不足还有我老年代嘛~
老年代
老年代处于堆中,活得久的对象都在这里。当年轻代放不下大对象时,将大对象和活得久的对象放到老年代,老年代的特点是回收频率低、这里的对象都长寿,没有其他内存对此代做后备。
永久代
永久代是 HotSpot 虚拟机中的概念,在堆中实现了方法区,与年轻代和老年代分堆内存。由于方法区中存有大量类元数据与字面量常量池,类元数据卸载条件苛刻等原因常驻此代,几乎不发生GC,永久代由此得名。同时也因为类元数据难以卸载,当永久代内存到达设置值时,容易发生OOM。
在Oracle JDK 1.8之后,将永久代移出了 JVM 堆内存,进入直接内存并改名为元空间,按直接内存回收内存的方式管理(不设定元空间大小时,只受限于剩余内存的余量;设定元空间最大值时,则在接近满时,由 JVM 进行一次垃圾回收,不再像堆中回收规则那么多。可以说是永久代从亲娘家搬到了后娘家,不只改了姓,还变成了后娘养的😆)。
元空间
元空间是JDK 1.8后,使用直接内存(或称 堆外内存 或 非堆内存)实现了方法区,作为永久代的替代品。元空间不再分配在堆上,所以年轻代与老年代的可用内存会更多,也避免了因永久代 OOM 出现的 Full GC,减少了STW(Stop The World)与 GC(垃圾回收)的频率,提高了性能。
元空间与永久代区别
- 分配位置:永久代分配在堆上;元空间分配在堆外内存。
- 回收时刻:永久代与老年代几乎同时进行GC,回收频率较高;元空间使用量到达设置最大值前才会进行GC,默认此值无限,受物理内存限制。
- 设置最大内存值:永久代在划分最大值时就直接占用最大值空间;而元空间则依旧按原来的方式,用一些就申请稍大一些的内存,直到到达最大值后不再申请新内存。