『JVM』有哪些垃圾收集机制?

参考《深入理解Java虚拟机》周志明

一、概述

1、概念提要

垃圾收集(Garbage Collection)通常简称为GC,虽然垃圾收集是由虚拟机“自动化”完成的,但当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,作为一名合格的开发人员应该对这些“自动化”的技术实施必要的监控和调节

我们在之前的『JVM』什么是Java内存区域(运行时数据区)?这篇文章中讲过,线程私有的程序计数器、虚拟机栈、本地方法栈这三个区域的内存分配(几乎固定)和回收(方法或线程结束时)都具备确定性。而线程共享的Java堆和方法区这两个区域则具备显著的不确定性,这部分内存的分配和回收是动态的,因此垃圾收集器所关注的主要是这部分内存区域。

方法区的垃圾收集的“性价比”通常是很低的,主要回收两部分内容:废弃的常量不再使用的类型

不再使用的类型判定条件:

  • 该类所有的实例都已经被回收
  • 加载该类的类加载器已经被回收(很难达成)
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

2、Java堆结构划分

首先我们先来了解HotSpot虚拟机(Java默认的虚拟机)中对Java堆的结构划分,如图(来自网络):

image-20200901104435368

在该虚拟机中,将新生代分为Eden和两块较小的Survivor空间(From Survivor和To Survivor),默认的大小比例是Eden:From Survivor:To Survivor=8:1:1,也就是每次新生代中可用内存空间为整个新生代容量的90%,只有一个Survivor空间会被浪费(原因下文会提及)。

JVM允许我们人为地控制堆中各区域的大小,相关参数有:

  • -Xms(memory startup):设置堆的最小空间大小
  • -Xmx(memory maximum):设置堆的最大空间大小
  • -Xmn(memory nursery/new):设置堆中新生代初始以及最大大小
    • -XX:NewSize:设置新生代最小空间大小
    • -XX:MaxNewSize:设置新生代最大空间大小
  • -XX:PermSize:设置永久代最小空间大小
  • -XX:MaxPermSize:设置永久代最大空间大小
  • -Xss(stack size):设置每个线程的堆栈大小

* -:标准VM选项,VM标准的选项

  • -X:非标准VM选项,不保证所有VM支持
  • -XX:高级选项,高级特性,但属于不稳定的选项

顺便提一下,网上有很多关于IDEA调优的方法,其中绝大部分都是通过修改上面的某些参数来实现的:

image-20200901111448679

二、如何判断对象存活

判断对象是否存活通常有两种算法:引用计数算法和可达性分析算法。

1、引用计数算法

这个简单的算法很多人应该已经见过了:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就加一;当引用失效时,计数器的值就减一;任何时刻计数器为零的对象就是不可能再被使用的,这时就可以回收。

虽然这个算法的实现简单、判断高效,但是在主流的Java虚拟机中并没有采用,主要是单纯的引用计数很难解决对象之间互相循环引用的问题

2、可达性分析算法

这个算法的基本思想就是通过一系列称为GC Roots的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程中所走过的路径称为引用链。如果某个对象到GC Roots之间没有任何引用链相连(GC Roots到这个对象不可达),证明此对象是不可能再被使用的。

注意:不可达的对象并不是“非死不可”,要真正宣告一个对象死亡,通常要经历两次标记过程:第一次即为上面的可达性分析,不可达的对象经历一次标记;第二次则是对F-Queue中的对象进行标记。

如果对象覆盖了finalize()方法或者finalize()方法没有被虚拟机调用过,则该对象会被放入名为F-Queue队列中(其他没放入该队列的对象,就被判为“死亡”)。finalize()方法是对象逃脱死亡的最后一次机会,F-Queue中的对象可以通过finalize()方法重新建立引用链。任何对象的finalize()方法只会被系统自动调用一次,如果对象面临下一次回收,finalize()方法将不会再次被执行。

如下图(来自网络),绿色对象为存活对象,灰色对象为可回收对象(虽然内部相连,但到GC Roots是不可达的)。

image-20200901120434911

在Java技术体系里,固定可作为GC Roots的对象包括以下几种(面试常考):

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 在方法区中类静态属性引用的对象
  • 在方法区中常量引用的对象
  • 在本地方法栈中JNI(Native方法)引用的对象
  • Java虚拟机内部的引用
  • 所有被同步锁(synchronized关键字)持有的对象
  • 反映Java虚拟机内部情况的JMXBean,JVMTI中注册的回调、本地代码缓存等

三、垃圾收集算法

从上面判定对象是否死亡的角度出发,垃圾收集算法可以划分为:引用计数式垃圾收集(直接垃圾收集)和追踪式垃圾收集(间接垃圾收集)。下面主要介绍的算法均属于追踪式垃圾收集(间接垃圾收集)的范畴。

在第一部分概述中,我们已经了解了Java堆结构的划分。在当前商业虚拟机的垃圾收集器,大多都遵循了“分代收集”的理论进行设计的。先来了解下分代收集的相关名词:

  • 部分收集(Partial GC):目标不是整个Java堆的垃圾收集
    • 新生代收集(Minor GC/Young GC):目标只是新生代的垃圾收集
    • 老年代收集(Major GC/Old GC):目标只是老年代的垃圾收集(Major GC有时也指整堆收集)
    • 混合收集(Mixed GC):目标是整个新生代和部分老年代的垃圾收集
  • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集

垃圾收集器通常是建立在几个分代假说之上的:

  • 弱分代假说:绝大多数对象都是朝生夕灭的

  • 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡

  • 跨代引用假说:跨代引用相对于同代引用来说仅是占极少数

    如何代价最少地找到少量的跨代引用?

    在新生代上建立一个全局的记忆集,这个结构把老年代划分为若干小块,表示出老年代哪一块内存存在跨代引用。此后发生Minor GC时,不需要为了找到少量的跨代引用而扫描整个老年代了,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。

1、标记-清除算法

这个算法如同它的名字一样,分为标记、清除两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象(也可以标记存活的对象,回收所有未标记的对象)

主要缺点有:

  • 执行效率不稳定:大部分对象需要回收,随着对象数量增长,执行效率下降
  • 内存空间碎片化:每次清除之后,会产生大量的内存碎片。导致随后的内存分配无法找到连续的内存空间,而不得不再次执行标记-清除算法。

这是最基础的收集算法,后续的算法都是其改进的版本。

2、标记-复制算法(复制算法)

为了解决标记-清除算法的执行效率不稳定的缺点,提出了一种名为“半区复制”的垃圾收集算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。(图片来自网络)

image-20200901135306114

每次针对整个半区进行内存回收,分配内存时也不用考虑空间碎片的问题,只需要移动堆顶指针,按顺序分配即可。但是它也具有缺点:将可用内存缩小为原来的一半,空间浪费大;如果对象大多是存活的,会产生大量的复制开销。

现在大多数商用Java虚拟机都优先采用这种算法来回收新生代:

image-20200901104435368

在这种Java新生代结构中,每次只会使用Eden区和其中一块Survivor区进行对象的分配。当发生垃圾收集时,会将还存活的对象复制到另一块未使用的Survivor区,然后再对Eden区和使用过的Survivor区进行清理。

如果其中一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将通过分配担保机制直接进入老年代

新生代具有一个特点:新生代中98%的对象都熬不过第一轮的收集。在这里,你应该就清楚了,为什么Java堆的新生代比例会是Eden:From Survivor:To Survivor=8:1:1了吧,即每次存活的对象基本不多于10%。

3、标记-整理算法

针对老年代对象的存亡特征而设计。其中该算法的标记过程与标记-清除算法一样,但整理过程是让所有存活对象都向内存空间一端移动,然后直接清理掉边界以外的内存。(图片来自网络)

image-20200901141310063

在这种移动式的回收算法中,对象的移动操作必须暂停用户应用程序(Stop The World)才能进行

Stop The World在之后的垃圾收集器中会经常见到。

移动则会导致内存回收更复杂,而不移动(标记-清除算法)则会导致内存分配更复杂。因此,有些垃圾收集器将两者结合起来,平时大多数时间采用标记-清除算法,暂时容忍内存碎片的存在。等到内存碎片化程度大到影响分配对象时,再采用标记-整理算法收集一次,以获得规整的内存空间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值