理解 JVM GC 垃圾回收算法

JVM 堆根据对象的生命周期长短特点将其进行分块,根据每块内存区间的特点,使用不同的回收算法,新生代使用了复制算法,老年代使用了标记压缩清除算法, 目的是为了提高垃圾回收的效率。

这些算法是什么, 有什么不同? 究竟是如何提高效率的呢?

算法一复制算法(Java 中新生代采用)

核心思想是将内存空间分成两块,同一时刻只使用其中的一块,在垃圾回收时将正在使用的内存中的存活的对象复制到未使用的内存中,然后清除正在使用的内存块中所有的对象,然后把未使用的内存块变成正在使用的内存块,把原来使用的内存块变成未使用的内存块。很明显如果存活对象较多的话,算法效率会比较差,并且这样会使内存的空间折半,但是这种方法也不会产生内存碎片

Minor GC 会把 Eden 中的所有活的对象都移到 Survivor 区域中,如果 Survivor 区中放不下,那么剩下的活的对象就被移到 Old generation 中,也即一旦收集后,Eden 是就变成空的了。
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,通过 -XX:MaxTenuringThreshold 来设定参数),这些对象就会成为老年代。
 
-XX:MaxTenuringThreshold — 设置对象在新生代中存活的次数

算法二:标记清除法

这个方法是将垃圾回收分成了两个阶段:标记阶段和清除阶段

在标记阶段,通过跟对象,标记所有从跟节点开始的可达的对象,那么未标记的对象就是未被引用的垃圾对象

在清除阶段,清除掉所以的未被标记的对象

它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲

垃圾回收后可能存在大量内存碎片, 基本不使用该方法。对于这个算法改进的算法,即算法三

算法三:标记清除压缩法(Java 中老年代采用)

在算法二的基础上做了一个改进,分为三个阶段:标记阶段,压缩阶段,清除阶段。标记阶段和清除阶段不变,增加了一个压缩阶段,就是在做完标记阶段后,

将这些标记过的对象集中放到一起,确定开始和结束地址,比如全部放到开始处,这样再去清除,将不会产生磁盘碎片。但是我们也要注意到几个问题,压缩阶段占用了系统的消耗

并且如果标记对象过多的话,损耗可能会很大,在标记对象相对较少的时候,效率较高

 

附: 什么是 新生代 / 老年代 / 永久代

新生代/年老代存在于堆中, 永久代又叫非堆, 方法区, java8 之后改为元空间

(1) 新生代:

新生代使用复制和标记 - 清除垃圾收集算法,研究表明,新生代中 98% 的对象是朝生夕死的短生命周期对象,所以不需要将新生代划分为容量大小相等的两部分内存,而是将新生代分为 Eden 区,Survivor from 和 Survivor to 三部分,其占新生代内存容量默认比例分别为 8:1:1,其中 Survivor from 和 Survivor to 总有一个区域是空白,只有 Eden 和其中一个 Survivor 总共 90% 的新生代容量用于为新创建的对象分配内存,只有 10% 的 Survivor 内存浪费,当新生代内存空间不足需要进行垃圾回收时,仍然存活的对象被复制到空白的 Survivor 内存区域中,Eden 和非空白的 Survivor 进行标记 - 清理回收,两个 Survivor 区域是轮换的。

新生代中 98% 情况下空白 Survivor 都可以存放垃圾回收时仍然存活的对象,2% 的极端情况下,如果空白 Survivor 空间无法存放下仍然存活的对象时,使用内存分配担保机制,直接将新生代依然存活的对象复制到年老代内存中,同时对于创建大对象时,如果新生代中无足够的连续内存时,也直接在年老代中分配内存空间。

Java 虚拟机对新生代的垃圾回收称为 Minor GC,次数比较频繁,每次回收时间也比较短。

使用 Java 虚拟机 - Xmn 参数可以指定新生代内存大小。

(2) 老年代:

年老代中的对象一般都是长生命周期对象,对象的存活率比较高,因此在年老代中使用标记 - 整理垃圾回收算法。

Java 虚拟机对年老代的垃圾回收称为 MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。

当新生代中无足够空间为对象创建分配内存,年老代中内存回收也无法回收到足够的内存空间,并且新生代和年老代空间无法在扩展时,堆就会产生 OutOfMemoryError 异常。

java 虚拟机 - Xms 参数可以指定最小内存大小,-Xmx 参数可以指定最大内存大小,这两个参数分别减去 Xmn 参数指定的新生代内存大小,可以计算出年老代最小和最大内存容量。

(3) 永久代:

Java 虚拟机内存中的方法区在 Sun HotSpot 虚拟机中被称为永久代,是被各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。永久代垃圾回收比较少,效率也比较低,但是也必须进行垃圾回收,否则会永久代内存不够用时仍然会抛出 OutOfMemoryError 异常。

永久代也使用标记 - 整理算法进行垃圾回收,java 虚拟机参数 - XX:PermSize 和 - XX:MaxPermSize 可以设置永久代的初始大小和最大容量。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值