java虚拟机--经典垃圾收集器

在这里插入图片描述
图中展示了其中作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用,图中收集器所处的区域,则表示它是属于新生代收集器还是老年代收集器
垃圾收集器根据功能一般可以分为两类:追求高吞吐量(即让cpu更多的用于处理用户线程上,某些收集器在收集阶段需要与用户线程并发执行,因此导致占用cpu处理用户线程的时间,会影响吞吐量);还有一类追求低的停顿时间,达到这个要求的可以有两种途径:一是暂停用户线程,让多条收集器线程并行执行,但是这样在垃圾收集的时候无法响应用户的请求,二是:与用户线程并发执行,这样在垃圾收集的时候可以响应用户请求

Serial收集器(新生代)
  • 该收集器是一个单线程工作的收集器,不仅仅使用一条垃圾收集线程完成垃圾收集工作,而且该垃圾收集器进行垃圾收集工作(标记-复制)时,必须暂停其他所有工作的线程(Stop The World),直到垃圾收集结束。
  • Serial/Serial Old垃圾收集器运行示意图
    无论是标记-复制还是标记-整理垃圾算法,标记过程都是一样的:当发生垃圾收集时,通过主动式中断,所有用户线程都到达安全点,然后将自己挂起,然后GC线程枚举根节点,进行可达性分析,标记那些需要被清除的对象。在新生代采用Serial垃圾收集器,通过标记-复制垃圾收集算法;在老年代采用Serial Old垃圾收集器,通过标记–整理来进行垃圾收集在这里插入图片描述
  • 该收集器依然是Hot Spot虚拟机运行在客户端模式下默认的新生代垃圾收集器,优点:(1)对于内存资源受限的环境,它是所有垃圾收集器里额外内存消耗最小的;(2)对于单核处理器或者处理器核心数较少的环境,Serial收集器由于没有线程交互的开销,专心垃圾收集自然可以获得最高的单线程收集效率。
    一般用户桌面的应用或者微服务应用,分配给虚拟机管理的内存一般不会太大,收集几十兆甚至一两百兆的新生代,垃圾收集停顿时间完全可以控制在十几、几十毫秒最多一百多毫秒以内,只要不频繁发生垃圾收集,这是可以接受的。
ParNew收集器(新生代)
  • 该垃圾收集器实质是Serial收集器的多线程并行版本,即在进行垃圾收集时,多条垃圾收集线程并行的对新生代进行垃圾收集。在这里插入图片描述
  • ParNew收集器是运行在服务端模式下的HotSpot虚拟机的首选,除了Serial收集器外,目前只有ParNew收集器能与CMS收集器配合使用。
  • ParNew收集器是激活CMS后,(使用-XX:+UserConcMarkSweepGC选项)的默认收集器,也可以使用-XX:+/-UseParNewGC选项来强制指定它
  • ParNew收集器在单核心处理器的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该手奇迹在通过超线程技术实现的伪双核处理器环境中都不能保证超越Serial收集器。随着处理器核心数增加,ParNew的效率得到了提现。
  • ParNew默认开启的收集线程数等于处理器核心数,在核心数非常多的环境中,通过-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
Parallel Scavenge收集器(新生代)
  • 该收集器同样基于标记-复制算法(新生代收集器都是基于标记-复制算法),同时也是能够多个垃圾收集线程并行收集垃圾( 该收集器与ParNew非常相似)
    *但是该收集器与其他收集器的不通点: Paralle Scavenge收集器的目标是达到一个可控制的吞吐量(处理器用于运行用户代码的时间/处理器总消耗时间(总消耗时间:运行用户代码的时间+运行垃圾回收的时间)),而其他垃圾收集的的目标是:尽可能的缩短垃圾收集时用户线程的停顿时间。用户线程停顿时间越短,那么越适合需要与用户交互或者保证服务相应质量的程序,良好的相应速度提升用户体验;高吞吐量则可以高效率的利用处理器资源,尽快完成程序的运算任务,适合在后台运算而不需要过多交互的分析任务。
  • Parallel Scavenge收集器,提供了两个参数精确控制吞吐量:
    • -XX:MaxGCPauseMillis
      (1)该参与用于设置最大垃圾收集停顿时间
      (2)以毫秒为单位,收集器尽量保证内存回收花费的时间不超过该设定的时间。
      (3)如果时间设置的太短,虽然能够让一次垃圾收集的时间变短,但是会使得新生代的空间变小,这样会导致收集的频率变高。
    • -XX:GCTimeRatio
      (1)参数是一个0-100的整数,即垃圾收集器占总时间的比率,比如设置为19,那么最大的垃圾收集占总时间为1/(1+19).
  • -XX:UseAdaptiveSizePolicy参数:当这个参数被激活,那么不用人工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象的大小(-XX:PretenureSizeThreshold)等参数细节,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最适合的停顿时间或者最大吞吐量。
    这种调节方式,称为Parallel Scavenge收集器自适应调节策略,把内存管理的调优任务交给虚拟机自己完成
Serial Old收集器(老年代)
  • Serial Old是Serial的老年代版本,同样是单线程收集器,使用标记-整理算法(老年代的收集器都是用标记-整理算法,不用标记-复制算法是因为标记复制算法需要分配担保)。
  • 与Serial收集器一样,该收集器在HotSpot虚拟机客户端模式下使用(客户端模式下,虚拟机管理的内存不大,用单线程收集垃圾够了)。
  • 如果该收集器工作在服务端模式下,可能有两种用途:(1)一种是在JDK5以及之前的版中与Parallel Scavenge收集器搭配使用(2)作为CMS收集器发生失败时的后备预案,并在并发收集发生Concurrent Mode Failure时使用。
Parallel Old收集器(老年代)
  • Parallel Old是Parallel Scavenge的老年代版本,支持多线程并行收集,基于标记-整理算法实现
  • 该收集器一般搭配新生代的Parallel Scavenge收集器,在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge搭配Parallel Old收集器组合,
  • 工作流程图
    在这里插入图片描述
CMS(Concurrent Mark Sweep,即并发标记清除)收集器(老年代)
  • CMS收集器目标:获得最短的回收停顿时间,这样让系统具有更快的响应速度
  • 该收集器使用标记-清除回收算法
  • 该收集的优点:并发收集,低停顿,故也叫并发低停顿收集器
  • 垃圾回收过程
    • 初始标记(initial mark)
      (1)标记的内容:初始标记仅仅标记一下GC Roots能直接关联到的对象
      (2)特点:该步骤速度很快,需要Stop The World
    • 并发标记(concurrent mark)
      (1)从GC Roots直接关联的对象开始遍历整个对象图的过程
      (2)特点:该步骤耗时长,不需要Stop The World,即垃圾回收线程与用户线程并发执行
    • 重新标记(remark)
      (1)该过程是为了修正并发标记阶段,因为用户线程继续运作导致标记产生变动的那部分对象的标记记录(即之前说的并发标记中的增量更新:如果黑色对象插入了指向白色对象的引用,那么黑色对象变为灰色,之后对灰色对象中的引用进行遍历标记)
      (2)该步骤需要Stop The World,并且停顿的时间稍微长于初始标记,但是远远短于并发标记
    • 并发清除(concurrent sweep)
      (1)清除掉标记阶段判断为死亡的对象,
      (2)特点:由于是直接清除,故不需要移动存活的对象,故该阶段可以和用户线程并发。
  • Concurrent Mark Sweep收集器的运行示意图
    在这里插入图片描述
  • 该收集器的缺点:
    • (1)CMS收集对处理器资源非常敏感,在并发阶段(并发标记、并发清除)因为占用了一部分线程而导致应用程序变慢(该部分线程会与用户线程争夺CPU资源,导致用户线程执行时间变少),降低总吞吐量。CMS默认启动的回收线程数量=(处理器的核心数+3)/4。
    • (2)CMS无法处理浮动垃圾,有可能出现**“Concurrent Mode Failure”失败**进而导致另一次完全“Stop The World”的Full GC产生。
      浮动垃圾:并发清除阶段,由于用户线程还是在执行,这样就会伴随这垃圾产生,但是这部分垃圾出现在标记过程结束之后,CMS只能在下一次垃圾收集时候清理掉他们。这些本次垃圾回收没有被清理的垃圾,叫做浮动垃圾。
      同样在并发清除阶段用户线程还在运行,还就需要预留足够的内存空间提供给用户线程使用,因此CMS不能像其他收集器那样等待老年代空间几乎被填满再收集,必须预留一部分空间供并发收集时的程序运行使用。
      在JDK6,CMS收集器的启动阈值已经默认提升到92%(即老年代中内存的使用占总使用量的92%),当CMS运行期间预留的内存无法满足程序分配新对象时,就会发生Concurrent Mode Failure(并发失败)的错误,这时候虚拟机冻结用户线程的执行,临时启动Serial Old收集器来重新进行老年代的垃圾收集。可以通过-XX:CMSInitialatingOCcupancyFraction参数,控制触发cms垃圾收集器的百分比。
    • (3)CMS采用的是标记-清除算法,故会在内存中产生大量的内存碎片
      空间碎片过多,往往会出现,老年代还有很多剩余空间,但是由于无法找到足够大的连续空间来存储当前对象,而触发Full GC。
Garbage First(G1)收集器
  • G1收集器开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
  • G1是一款面向服务端应用的垃圾收集器,是服务器端的默认收集器。
  • G1收集器出现之前,其他的收集器包括CMS,垃圾收集的目标范围要么是新生代(Minor GC),要么就是整个老年代(Major GC),要么就是整个java 堆(Full GC),而G1收集器可以面向堆内存任何部分来组成回收集(Collection Set即CSet)进行回收,垃圾回收区域衡量的标准不再是属于哪个代,而是那块内存中存放的垃圾数量最多,回收收益最大。
  • G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每个Region都可以根据需要,扮演新生代的Eden空间,Survivor空间,或者是老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理。
  • Region还有一类特殊的Humongous区域,专门用来存储大对象。只要对象大小超过了一个Region容量的一半,就是大对象。如果一个对象超过了整个Region容量,会被存放到N个连续的Humongous Region之中。在G1中一般把Humongous Region区域看作是老年代。每个Region可以通过-XX:G1HeapRegionSize设定大小,取值范围为1M-32MB,且必须是2的N次幂。
  • G1之所以能够建立可预测的停顿时间模型:堆中被划分为若干Region,若干不连续的Region组成老年代或者新生代,G1收集器跟踪各个Region里面的垃圾堆积的“价值”大小(价值即为:对该Region进行垃圾回收得到的空间以及回收所需要的时间的经验值),在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用-XX:MaxGCPauseMillis指定,默认是200毫秒),优先处理回收价值收益最大的Region,这一个Garbage First名字的由来。
  • G1收集器Region分区示意图
    堆被花纹划分为若干Region,标志为E的Region 表示Eden区,标志为S的属于Survivor区,标志为H的为Houmongous区域,也被视为老年代
    在这里插入图片描述
  • 在G1收集器中关键细节的解决方式
    • 存在跨Region引用:使用记忆集来解决,每个Region都维护有自己的记忆集,这些记忆集会记录别的Region指向自己的指针(即本Region中的对象被其他Region所引用),并标记这些指针分别在哪些卡页的范围内。G1的记忆集本质是一个哈希表,Key:别的Region的起始地址,Value:是一个集合,里面存储的元素是卡表的索引号(一个索引号就对应着一块内存区域,表示该内存区域中存在跨区域引用)。由于Region数量多,并且每个Region都要维护一个记忆集,故G1收集器有着更高的内存占用,一般G1消耗大约相当于Java堆容量10%至20%的额外内存维持收集器工作。
    • 并发标记阶段必须保证标记线程(即收集器线程)与用户线程互不干扰:(1)用户线程可能改变并发标记阶段的引用关系:CMS用增量更新解决,G1用原始快照算法实现。(2)在并发标记阶段,用户线程在堆中创建对象,G1为每一个Region设计两个名为TAMS(Top at Mark Start)指针,把Region中一部分空间划分出来用于并发标记过程中新对象的分配,并发标记时新分配的对象地址都必须要在这两个指针位置以上。G1收集器迷人在这个地址以上的对象是被隐式标记过的,即默认其是存活的,不纳入回收范围。如果内存回收的速度赶不上内存分配的速度,G1收集器会冻结用户线程,进行Full GC.
    • 如何建立可靠的停顿模型,即用户设定了-XX:MaxGCPauseMillis参数设置期望的停顿时间后,G1如何满足这个期望值:G1收集器的停顿预测模型事宜衰减均值(与普通的平均值相比更容易收到新数据的影响)为理论基础,在垃圾收集过程中,G1收集器会记录每个Region的回收耗时、每个Region记忆集中的脏卡数量(即该Region中存在的跨域引用多少)等各个可以测量的步骤花费的成本,并分析得出均值、标准偏差、置信度等统计信息。
  • G1垃圾回收大致过程
    • 初始标记
      (1)标记内容:标记GC Roots直接关联的对象,修改TAMS指针,让下阶段用户线程并发运行时,能够正确的在可用的Region中分配新对象。
      (2)特点:需要Stop The World,耗时短,是在新生代进行GC的过程中同步完成了老年代的初始标记,故G1在初始标记阶段实际并没有额外停顿。
    • 并发标记(只有该阶段不需要Stop The World)
      (1)标记内容:从GC Root直接关联的对象开始在堆中对象进行可达性分析,递归扫描整个堆中的对象图,找出要回收的对象。
      (2)特点:耗时长,可以与用户线程并发执行。当独享扫描完成后,还要重新处理SATB记录下的在并发标记时有引用变动对象。
    • 最终标记
      (1)标记内容:处理并发标记阶段结束后遗留下来的少量的SATB(原始快照搜索)记录
      (2)特点:耗时短,需要Stop The World
    • 筛选回收
      (1)内容:复制更新Region统计数据,对各个Region的回收价值和成本进行排序,根据用户期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那部分Region(即回收集中的Region)中存活的对象,复制到空的Region中,在清理掉整个旧Region的全部空间
      (2)特征:该操作涉及存活对象的移动,需要Stop The World,又多条线程并行完成。
      在这里插入图片描述
      G1收集器除了并发标记外,其余阶段也是完成暂停用户线程,即G1线程不是纯粹追求低延迟,而是在延迟可控的情况下获得尽可能高的吞吐量
  • G1在回收阶段默认的停顿目标是200毫秒,如果认为的把期望停顿的时间调的太低,比如20毫秒,就可能导致由于停顿目标时间太短,每次选出来的回收集只占堆内存的很少一部分,收集器收集的速度逐渐跟不上分配器分配的速度,导致垃圾慢慢堆积,最终触发Full GC导致性能下降,一般把期望值调到一两百毫秒或者三四百毫秒比较合理。
  • G1收集器与CMS的比较
    (1)相比CMS,G1的优点有:可以指定最大停顿时间;分Region的内存布局;按收益动态确定回收集(即:需要回收哪些Region);CMS采用标记-清除算法,这样CMS会产生大量的内存碎片,而G1从整体上看基于标记-整理算法实现,从局部(两个Region之间)上看基于标记-复制算法实现,这样G1就不会产生内存碎片,并且垃圾收集完成后内存规整。如果存在大量垃圾碎片,如果有大对象需要分配空间时,没有足够连续的空间供分配,就会触发垃圾收集。
    (2)相比CMS,G1的劣势:用户程序运行过程中,G1无论是为垃圾收集产生的内存占用还是程序运行时额外执行负载都要比CMS高。
    内存占用:由于可能存在跨Region引用,每个Region都有记忆集,占用内存,而CMS只需要新生代维护记忆集,而且记忆集实现简单,老年代不需要维护(因为老年代不容易发生GC,但是当老年代发生GC时,就需要把新生代作为GC Roots来进行扫描,防止老年代的对象被新生代引用而被误删除。
    额外执行负载:CMS用写后屏障来更新维护卡表,而G1除了使用写后屏障来维护卡表外,为了实现原始快照搜索算法(在并发标记阶段实现的算法,该阶段有用户线程执行,故会使得引用发生变化),需要使用写前屏障来跟踪并发标记时的指针变化情况
    (3)在小内存应用上CMS的表现大概率由于G1,而在大内存应用上G1大多能发挥其优势,这样的优势的JAVA堆容量平衡点通常在6GB-8GB之间(即当JAVA堆的容量在6GB-8GB时,采用G1更加有优势),
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值