《JVM系列》 第九章 -- 垃圾回收器,2024年是做Java开发人员的绝佳时机


**如果你想要最小化地使用内存和并行开销,请选Serial GC

如果你想要最大化应用程序的吞吐量,请选Parallel GC

如果你想要最小化GC的中断或停顿时间,请选CMS GC**

G1回收器:区域化分代式


随着应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有GC就不能保证应用程序正常进行,而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化。G1(Garbage-First)垃圾回收器是在Java7 update4之后引入的一个新的垃圾回收器,是当今收集器技术发展的最前沿成果之一。

与此同时,为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同时兼顾良好的吞吐量。

官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起“全功能收集器”的重任与期望

为什么名字叫 Garbage First(G1)呢?

  • 因为G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的Region来表示Eden、幸存者0区,幸存者1区,老年代等。

  • G1 GC有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

  • 由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(Garbage First)。

G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。

在JDK1.7版本正式启用,移除了Experimental的标识,是JDK9以后的默认垃圾回收器,取代了CMS回收器以及Parallel+Parallel old组合。被oracle官方称为 “全功能的垃圾收集器”。

G1垃圾收集器的优点:

并行与并发:

  • 并行性: G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力。此时用户线程STW
  • 并发性: G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况

分代收集:

  • 从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden区和Survivor区。但从堆的结构上看,它不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。
  • 将堆空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代。
  • 和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代;

在这里插入图片描述

空间整合:

  • CMS: “标记-清除”算法、内存碎片、若干次GC后进行一次碎片整理
  • G1将内存划分为一个个的region。内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记-压缩(Mark-Compact)算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。尤其是当Java堆非常大的时候,G1的优势更加明显。

可预测的停顿时间模型(即:软实时soft real-time):

这是G1相对于CMS的另一大优势,G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

  • 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。
  • G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
  • 相比于CMSGC,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。

G1垃圾收集器的缺点:

  • 相较于CMS,G1还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(overload)都要比CMS要高。

  • 从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间。

G1参数设置:

  • -XX:+UseG1GC:手动指定使用G1垃圾收集器执行内存回收任务

  • -XX:G1HeapRegionSize:设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。

  • -XX:MaxGCPauseMillis: 设置期望达到的最大Gc停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms

  • -XX:+ParallelGcThread: 设置STW工作线程数的值。最多设置为8

  • -XX:ConcGCThreads: 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGcThreads)的1/4左右。

  • -XX:InitiatingHeapoccupancyPercent:设置触发并发Gc周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。

G1收集器的常见操作步骤:

  • 第一步:开启G1垃圾收集器

  • 第二步:设置堆的最大内存

  • 第三步:设置最大的停顿时间

G1中提供了三种垃圾回收模式:YoungGC、Mixed GC和FullGC,在不同的条件下被触发。

G1收集器的适用场景:

  • 面向服务端应用,针对具有大内存、多处理器的机器。(在普通大小的堆里表现并不惊喜)

  • 最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案;

  • 如:在堆大小约6GB或更大时,可预测的暂停时间可以低于e.5秒;(G1通过每次只清理一部分而不是全部的Region的增量式清理来保证每次Gc停顿时间不会过长)。

  • 用来替换掉JDK1.5中的CMS收集器;在下面的情况时,使用61可能比CMS好:

①超过5e%的Java堆被活动数据占用;

②对象分配频率或年代提升频率变化很大;

③GC停顿时间过长(长于e.5至1秒)

  • HotSpot垃圾收集器里,除了61以外,其他的垃圾收集器使用内置的JVM线程执行Gc的多线程操作,而G1GC可以采用应用线程承担后台运行的GC工作,即当JVM的GC线程处理速度慢时,系统会调用应用程序线程帮助加速垃圾回收过程。

分区Region:化整为零

使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。可以通过XX:G1HeapRegionsize设定。所有的Region大小相同,且在JVM生命周期内不会被改变。

虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。

在这里插入图片描述

一个region有可能属于Eden,Survivor或者old/Tenured内存区域。但是一个region只可能属于一个角色。图中的E表示该region属于Eden内存区域,s表示属于survivor内存区域,o表示属于0ld内存区域。图中空白的表示未使用的内存空间。

G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。主要用于存储大对象,如果超过1.5个region,就放到H。

设置H的原因:

对于堆中的对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放大对象。**如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。**为了能找到连续的H区,有时候不得不启动Fu11Gc。G1的大多数行为都把H区作为老年代的一部分来看待。

每个Region都是通过指针碰撞来分配空间。

G1垃圾回收器的回收过程


  • 年轻代GC(Young GC)

  • 老年代并发标记过程(Concurrent Marking)

  • 混合回收(Mixed GC)

  • 如果需要,单线程、独占式、高强度的FullGC还是继续存在的。它针对GC的评估失败提供了一种失败保护机制,即强力回收。)

在这里插入图片描述

  • 应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程;G1的年轻代收集阶段是一个并行的独占式收集器。在年轻代回收期,G1GC暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到Survivor区间或者老年区间,也有可能是两个区间都会涉及。

  • 当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程。

  • 标记完成马上开始混合回收过程。对于一个混合回收期,G1GC从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了。同时,这个老年代Region是和年轻代一起被回收的。

举个例子: 一个Web服务器,Java进程最大堆内存为4G,每分钟响应1500个请求,每45秒钟会新分配大约2G的内存。G1会每45秒钟进行一次年轻代回收,每31个小时整个堆的使用率会达到45%,会开始老年代并发标记过程,标记完成后开始四到五次的混合回收。

Remembered Set(记忆集):

  • 一个对象被不同区域引用的问题
  • 一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确?
  • 在其他的分代收集器,也存在这样的问题(而G1更突出)回收新生代也不得不同时扫描老年代?这样的话会降低MinorGC的效率;

解决方法:

  • 无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描:
  • 每个Region都有一个对应的Remembered Set;
  • 每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作;
  • 然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象);
  • 如果不同,通过cardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中;
  • 当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set;
  • 就可以保证不进行全局扫描,也不会有遗漏。

在这里插入图片描述

G1回收过程1 - 年轻代GC

  • JVM启动时,G1先准备好Eden区,程序在运行过程中不断创建对象到Eden区,当Eden空间耗尽时,G1会启动一次年轻代垃圾回收过程。年轻代回收只会回收Eden区和Survivor区。
  • YGC时,首先G1停止应用程序的执行(stop-The-Wor1d),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区所有的内存分段。

然后开始如下回收过程:

  • 第一阶段,扫描根
根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同RSet记录的外部引用作为扫描存活对象的入口。
  • 第二阶段,更新RSet
处理dirty card queue(见备注)中的card,更新RSet。此阶段完成后,**RSet可以准确的反映老年代对所在的内存分段中对象的引用。**
  • 第三阶段,处理RSet
识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。
  • 第四阶段,复制对象
此阶段,对象树被遍历,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到o1d区中空的内存分段。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间。
  • 第五阶段,处理引用
处理Soft,Weak,Phantom,Final,JNI Weak 等引用。最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。

在这里插入图片描述

G1回收过程2 - 并发标记过程:

  • 初始标记阶段: 标记从根节点直接可达的对象。这个阶段是sTw的,并且会触发一次年轻代GC。
  • 根区域扫描(Root Region Scanning): G1 Gc扫描survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在youngGC之前完成。
  • 并发标记(Concurrent Marking): 在整个堆中进行并发标记(和应用程序并发执行),此过程可能被youngGC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。 同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
  • 再次标记(Remark): 由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning(SATB)。
  • 独占清理(cleanup,STW): 计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是sTw的。这个阶段并不会实际上去做垃圾的收集
  • 并发清理阶段: 识别并清理完全空闲的区域。

G1回收过程3 - 混合回收:

当越来越多的对象晋升到老年代o1d region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个old GC,除了回收整个Young Region,还会回收一部分的old Region。这里需要注意:是一部分老年代,而不是全部老年代。可以选择哪些o1d Region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC。

  • 并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次(可以通过-XX:G1MixedGCCountTarget设置)被回收
  • 混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。具体过程请参考上面的年轻代回收过程。
  • 由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收,-XX:G1MixedGCLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。
  • 混合回收并不一定要进行8次。有一个阈值-XX:G1HeapWastePercent,默认值为1e%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于1e%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少。

在这里插入图片描述

G1回收可选的过程4 - Full GC:

  • G1的初衷就是要避免FullGC的出现。但是如果上述方式不能正常工作,G1会停止应用程序的执行(stop-The-world),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。
  • 要避免FullGC的发生,一旦发生需要进行调整。什么时候会发生Ful1GC呢?比如堆内存太小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到ful1gc,这种情况可以通过增大内存解决。
  • 导致G1FullGC的原因可能有两个:
①EVacuation的时候没有足够的to-space来存放晋升的对象;  
②并发处理过程完成之前空间耗尽。

G1回收的优化建议:

从oracle官方透露出来的信息可获知,回收阶段(Evacuation)其实本也有想过设计成与用户程序一起并发执行,但这件事情做起来比较复杂,考虑到G1只是回一部分Region,停顿时间是用户可控制的,所以并不迫切去实现,而选择把这个特性放到了G1之后出现的低延迟垃圾收集器(即ZGC)中。另外,还考虑到G1不是仅仅面向低延迟,停顿用户线程能够最大幅度提高垃圾收集效率,为了保证吞吐量所以才选择了完全暂停用户线程的实现方案。

年轻代大小:

  • 避免使用-Xmn或-XX:NewRatio等相关选项显式设置年轻代大小
  • 固定年轻代的大小会覆盖

暂停时间目标暂停时间目标不要太过严苛:

  • G1 GC的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间
  • 评估G1GC的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示你愿意承受更多的垃圾回收开销,而这些会直接影响到吞吐量。

垃圾回收器总结


截止JDK1.8,一共有7款不同的垃圾收集器。每一款的垃圾收集器都有不同的特点,在具体使用的时候,需要根据具体的情况选用不同的垃圾收集器。

在这里插入图片描述

怎么选择垃圾回收器?

Java垃圾收集器的配置对于JVM优化来说是一个很重要的选择,选择合适的垃圾收集器可以让JVM的性能有一个很大的提升。怎么选择垃圾收集器?

  • 优先调整堆的大小让JVM自适应完成。

  • 如果内存小于100M,使用串行收集器

  • 如果是单核、单机程序,并且没有停顿时间的要求,串行收集器

  • 如果是多CPU、需要高吞吐量、允许停顿时间超过1秒,选择并行或者JVM自己选择

  • 如果是多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1秒,如互联网应用),使用并发收集器

  • 官方推荐G1,性能高。现在互联网的项目,基本都是使用G1。

GC日志


通过阅读GC日志,我们可以了解Java虚拟机内存分配与回收策略。

内存分配与垃圾回收的参数列表:

  • -XX:+PrintGC:输出GC日志。类似:-verbose:gc

  • -XX:+PrintGcDetails:输出Gc的详细日志

  • -XX:+PrintGCTimestamps: 输出GC的时间戳(以基准时间的形式)

  • -XX:+PrintGCDatestamps :输出GC的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)

  • -XX:+PrintHeapAtGC:在进行GC的前后打印出堆的信息

  • -Xloggc:…/logs/gc.1og:日志文件的输出路径

YoungGC:

在这里插入图片描述

FullGC:

在这里插入图片描述

垃圾回收器的新发展


GC仍然处于飞速发展之中,目前的默认选项G1GC在不断的进行改进,很多我们原来认为的缺点,例如串行的FullGC、Card Table扫描的低效等,都已经被大幅改进,例如,JDK10以后,FullGC已经是并行运行,在很多场景下,其表现还略优于ParallelGC的并行Ful1GC实现。

即使是SerialGC,虽然比较古老,但是简单的设计和实现未必就是过时的,它本身的开销,不管是GC相关数据结构的开销,还是线程的开销,都是非常小的,所以随着云计算的兴起,在serverless等新的应用场景下,Serial Gc找到了新的舞台。

比较不幸的是CMSGC,因为其算法的理论缺陷等原因,虽然现在还有非常大的用户群体,但在JDK9中已经被标记为废弃,并在JDK14版本中移除

  • Epsilon:A No-Op GarbageCollector(Epsilon垃圾回收器,"No-Op(无操作)"回收器)

  • ZGC:A Scalable Low-Latency Garbage Collector(Experimental)(ZGC:可伸缩的低延迟垃圾回收器,处于实验性阶段)

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

总结

在这里,由于面试中MySQL问的比较多,因此也就在此以MySQL为例为大家总结分享。但是你要学习的往往不止这一点,还有一些主流框架的使用,Spring源码的学习,Mybatis源码的学习等等都是需要掌握的,我也把这些知识点都整理起来了

面试真题

Spring源码笔记

时减轻大家的负担。**
[外链图片转存中…(img-4Z57JCZg-1711160712795)]
[外链图片转存中…(img-5g9UEh3u-1711160712797)]
[外链图片转存中…(img-PVgLUtGD-1711160712797)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-pZFCm7Fn-1711160712798)]

总结

在这里,由于面试中MySQL问的比较多,因此也就在此以MySQL为例为大家总结分享。但是你要学习的往往不止这一点,还有一些主流框架的使用,Spring源码的学习,Mybatis源码的学习等等都是需要掌握的,我也把这些知识点都整理起来了

[外链图片转存中…(img-jXjoxxtP-1711160712798)]

[外链图片转存中…(img-ICkkQP24-1711160712799)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值