深入理解JVM系列——解析垃圾回收以及常见GC收集器

3. 1 对象的生存

在垃圾收集器对堆进行回收前,需要判断堆中的对象哪些存活,哪些已经死去。常用的两种方法:引用计数算法和可达性分析算法

**引用计数算法:**在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何适合计数器为零的对象就是不可能再被使用的。但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。

**可达性分析算法:**通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

 💡 在Java中固定可以作为GC Roots的对象包括: 1. 在虚拟机栈中局部变量表中引用的对象 2. 在本地方法栈中引用的对象 3. 在方法区中静态变量引用的对象 4. 在方法区中常量引用的对象 5. 被同步锁(synchronized)持有的对象 6. 虚拟机内容的一些引用,如基本数据类型对应的Class对象、一些常驻的异常对象等

</aside>

在可达性分析中被判断为死亡的对象不一定会被回收,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。没有必要执行finalize方法的对象会被二次标记,然后被回收。

3. 2 对象的引用

Java的引用可以分为4种,强引用、软引用、弱引用以及虚引用。

强引用: 最传统的引用,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

**软引用:**用来描述一些还有用但是非必须的对象。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。

**弱引用:**用来描述一些还有用但是非必须的对象。被弱引用关联的对象只能生成到下一次垃圾收集发生为止。

**虚引用:**最弱的一种引用关系。虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。对一个对象设置虚引用关联,只是为了在对象被收集器回收时收到系统通知。

软引用、弱引用以及虚引用都需要与一个引用队列一起使用。如果被引用的对象被回收,那么这个引用会加入到对应的引用队列当中。

3. 3 回收方法区

方法区也存在垃圾收集的行为,主要回收两部分内容:废弃的常量不再使用的类型

回收一个废弃常量与回收对象类似,当一个字符串常量“abc”没有被任务字符串对象引用,且虚拟机中也没有其他地方引用这个常量。那么这个常量就可以被回收。

判断一个类是否废弃的条件比较苛刻,需要同时满足:

 💡 1. 该类的所有实例对象都已经被回收

  1. 加载该类的类加载器也已经被回收

  2. 该类对应的Class对象没有在任何地方被引用,无法在任务地方通过反射访问这个类的方法

满足三个条件的无用类可以被GC回收,但不是一定会被回收。

3. 4 垃圾收集算法

大多数垃圾收集算法都遵循了“分代收集”的理论。一般将Java堆分为两个部分:新生代老生代。进一步划分为:Eden区,Survivor区Old 区。

 

**对象优先在Eden区进行分配。**当Eden区没有足够的空间时,虚拟机将发起一次Minor GC。

**大对象则直接进入到老年区。**大对象指需要大量连续内存空间的Java对象,比较典型的便是很长的字符串或者很长的数组。为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。

**长期存活的对象将进入到老年代。**对象在经历一次GC之后,如果还存活且能过被Servivor容纳的话,会被移动到Survivor区中,并且GC年龄+1 。当它的年龄超过阈值之后(默认15)会晋升到老年代中。

**动态对象年龄判定。**如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象可以直接进入到老年代,而无需达到阈值。

空间分配担保机制。在发生Minor GC前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 -XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者 -XX: HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次 Full GC。而在JDK6之后,规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC。

由于分代理论的存在,就自然存在这样的可能:新生代中的对象被老年代所引用,而为了找出存活的对象就不得不在GC Roots之后,再额外遍历整个老年代中所有的对象来进行分析,因此产生了跨代引用假说。存在相互引用关系的两个对象是应该倾向于同时生存或同时消亡的。只需要在新生代上建立一个全局的数据结构(记忆集),这个结构把老年代划分成若干个小块,标识出老年代的哪一块内存会存在跨代引用。

  • 标记-清除算法

    算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

    标记-清除算法的缺点有两个:1. 执行的效率不稳定。2. 内存空间的碎片化问题,会产生大量不连续的内存碎片

  • 标记-复制算法

    将可用内存的按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后将已使用过的内存空间一次清理掉。

     

    HotSpots虚拟机的Serial、ParNew等新生代收集器均采用了这种策略。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另一块Servivor空间上,然后直接清理掉Eden和已经使用过的那块Survivor空间。

    “标记-复制”算法不需要考虑有空间碎片的复杂情况,但是这种算法的代价是可用内存缩小为了原来的一半。

  • 标记-整理算法

    根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

     

3. 5 HopSpot的算法细节

  • 安全点:
  • 根节点枚举:
  • 安全区域
  • 记一记与卡表:

3. 6 垃圾收集器

  • Serial 收集器 (标记-复制算法)

    Serial 收集器是最基础的,采用单线程工作的收集器。它的 **“单线程”**的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。

  • Serial Old 收集器 (标记-整理算法

    Serial 收集器的老年代版本,它同样是一个单线程收集器。

  • ParNew 收集器(标记-复制算法)

    Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。通常和CMS进行搭配,ParNew对新生代进行垃圾回收,而CMS对老生代进行垃圾回收

  • Parallel Scavenge 收集器(标记-复制算法)

    Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)

  • Parallel Old 收集器(标记-整理算法)

    Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

  • CMS 收集器 (标记-清除算法)

    是一种以获取最短回收停顿时间为目标的收集器,HotSpot 虚拟机第一款真正意义上的并发收集器。

  • 初始标记:只标记和GC Roots能直接关联到的对象。

    并发标记:从GC Roots的直接关联对象开始遍历整个对象图来进行标记

    重新标记:为了修正在并发标记期间,因用户线程的运行而导致标记发生变化的对象的标记

    并发清理:开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

    优点: 低停顿、可以并发执行。

    缺点:对 CPU 资源敏感;无法处理浮动垃圾;它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。

    -XX:UseConcMarkSweepGC: 使用parNew+CMS+Serial Old的收集器组合

    -XX: CMSInitiatingOccupancyFraction: 设置CMS收集器在老年代空间被使用多少后触发回收,默认为80%。

  • G1 收集器 (整体上标记-清除算法;局部标记-复制算法)

    G1是一款面向服务器的垃圾收集器。G1将内存划分为多个独立的区域(Region),依旧采取了分代的思想但是不再坚持固定大小以及固定数量的分代区域划分,每个Region都可以根据需要扮演Eden区、Survivor区或者老年代空间。Region中还存在Humongous区专门用来保存大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。

    让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。 这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率。

    G1的停顿可预测,能明确在一个时间段内,消耗在垃圾收集器上的实际不能超过多长时间。(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒)。

     

     

    初始标记:只标记和GC Roots能直接关联到的对象。

    并发标记:从GC Roots的直接关联对象开始遍历整个对象图来进行标记

    最终标记:为了修正在并发标记期间,因用户线程的运行而导致标记发生变化的对象的标记

    筛选回收:负责更新Region的统计数据,对各个Region进行一个排序,根据用户期望的停顿时间来指定回收计划。(回收阶段是stop-the-world的,与CMS不同)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值