G1垃圾回收器详解
文章目录
- 前言
- 一、思考问题
- 二、官方文档
- 三、基本介绍
- 四、G1的内存模型
- 五、G1的标记过程
- 六、G1的垃圾回收
- 1、G1过程梳理
- 2、Young GC
- 3、Mixed GC
- 4、Full GC
- 七、参数介绍
- 八、典型问题
- 1、疏散失败(Evacuation Failure)
- 2、大对象分配(Humongous Allocation)
- 3、Young GC花费时间太长
- 4、Mixed GC耗时太长
- 总结
前言
本来不准备写关于G1垃圾回收器的文章,因为网上介绍的文章真的太多了,写出来容易千篇一律,有抄袭的嫌疑。
但由于最近工作中遇到了G1垃圾回收期的线上优化问题,查找了很多资料,最终还是决定做一个总结,也希望能对大家有所帮助。
一、思考问题
先抛出一些关于G1垃圾回收器的问题,如果你都能回答上来,说明真的吃透了G1垃圾回收器,那么这篇文章你可以跳过了。如果还存在疑问,希望本文能给你解惑。
- G1有哪些特点?
- G1是分区还是分代?
- G1的一个内存单元Region中可以同时包含年轻代和老年代吗?
- G1相比CMS有哪些优点?相比ZGC有什么缺点?
- G1配置过程中,有哪些重要参数和注意事项?
- G1的使用过程中,遇到过哪些问题,怎么解决的?
- G1怎么实现目标暂停时间的?
- G1垃圾回收的过程中哪些阶段会出现STW?
- G1中哪些情况对象会被转移到老年代Old Genetion?
- G1中触发GC的时机?
- G1有哪些缺点?
- G1的适用场景?
- G1能否支持上T的大内存?
- G1在哪些情况下会出现Full GC?
二、官方文档
官网:https://www.oracle.com/technical-resources/articles/java/g1gc.html
java8下的g1说明
java19下的g1说明
G1垃圾收集器详解
注意:
推荐使用的是什么版本的JDK就查看对应版本的官网文档说明,因为不同版本间的一些参数可能会有些细微的不同。由于目前主流还是使用java8,所以本文主要基于java8来对g1垃圾回收器展开介绍。
三、基本介绍
G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术,JDK9 默认就是使用的G1垃圾收集器。
G1其实是Garbage First
的意思,垃圾优先? 不是,是优先处理那些垃圾多的内存块
的意思。
Garbage-First (G1)垃圾收集器是一个服务器风格的垃圾收集器,针对具有大内存的多处理器机器,它试图在实现高吞吐量的同时,以较高的概率满足垃圾收集(GC)暂停时间目标。
G1中堆被划分为一组大小相等的堆区域,每个区域有一个连续的虚拟内存范围。G1执行并发全局标记阶段,以确定整个堆中对象的活性。在标记阶段完成后,G1知道哪些区域大部分是空的。它首先收集这些区域,这往往会产生大量的自由空间。这就是为什么这种垃圾收集方法被称为“垃圾优先”
。
顾名思义,G1将其收集和压缩活动集中在堆中可能充满可回收对象(即垃圾)的区域。G1使用暂停预测模型
来满足用户定义的暂停时间目标,并根据指定的暂停时间目标选择要收集的区域数
。
G1将对象从堆的一个或多个区域复制到堆的单个区域,并在进程中压缩和释放内存。这种疏散是在多处理器上并行执行的,以减少暂停时间并提高吞吐量
。因此,对于每次垃圾收集,G1都会持续地减少碎片
。
需要注意的是,G1并不是一个实时收集器。它满足设定的暂停时间目标,具有较高的概率,但不是绝对确定的
。根据以前收集的数据,G1估计在目标时间内可以收集多少个区域。因此,收集器拥有一个相当精确的区域收集成本模型,并使用该模型来确定在暂停时间目标内收集哪些区域以及收集多少个区域。
G1的第一个重点是为运行需要大堆且 GC 延迟有限的应用程序的用户提供解决方案。这意味着堆大小约为6 GB 或更大,并且稳定且可预测的暂停时间低于0.5秒
。
(注意:在Java19推荐10G或者更大的堆内存)
如果应用程序具有以下一个或多个特性,那么当今使用 CMS 或并行压缩运行的应用程序将从切换到 G1中受益。
- 堆内存超过6G且活跃的数据超过java堆内存的50%
- 对象分配和晋升的速度非常快
- 应用程序不希望垃圾回收或内存压缩的暂停时间超过0.5s到1s
G1和CMS有哪些区别?
G1被计划作为并发标记扫描收集器(CMS)的长期替代品,通过比较 G1和 CMS 的差异,可以帮助我们更好的理解G1,并合理的选用。
1、空间压缩:G1采用复制-整理算法,在压缩空间方面有优势,可以避免产生内存空间碎片,而CMS采用标记-清除算法,会产生较多的空间碎片
2、暂停时间的可控性:G1使用暂停预测模型
来满足用户定义的暂停时间目标,并根据指定的暂停时间目标选择要收集的区域数
。CMS无法设置目标暂停时间,暂停时间不可控。
3、内存模型方面:G1采用物理分区,逻辑分代,Eden,Survivor,Old区不在是连续的一整块内存,而是又不连续的内存区域Region组成。而CMS中Eden,Survivor,Old区是连续的一整块内存。
4、G1既可以收集年轻代,也可以收集老年代,而CMS只能在老年代使用。
G1和ZGC比较?
最核心的问题是G1未能解决复制-转移过程中准确定位对象地址的问题
,无法做到复制-转移过程的并行。
G1的优缺点
优点:
1、支持较大的内存
2、暂停时间可控
3、压缩空间,避免产生内存碎片
4、简单配置就能达到很好的性能
缺点:
1、记忆集RSet会占用比较大的内存,因此不建议在小内存下使用G1,推荐至少6G
2、对CPU的负载可能会更大一点
3、由于采用复制算法,GC垃圾回收过程对象复制转移会占用较多的内存,更容易出现回收失败(Allocation (Evacuation) Failure)的问题。
4、可能会降低吞吐量
虽然 G1收集器的垃圾收集暂停时间通常要短得多,但应用程序吞吐量也往往略低一些。相当于把一次垃圾回收的工作,分开多次进行执行(主要指老年代),单次暂停的时间虽然更加可控,但是由于每次垃圾回收的空间会更少,总体来说垃圾回收的效率会更低,暂停的总时间会更长,所以吞吐量往往会略低一些。
四、G1的内存模型
G1 是一个既分区也分代的垃圾收集器,这意味着 Java 对象堆(堆)被划分为许多大小相同的区域。在启动时,Java 虚拟机(JVM)设置区域大小。根据堆大小,区域大小可以从1MB 到32MB 不等。目标是不超过2048个地区。伊甸园、幸存者和老一代是这些区域的逻辑集合,并不相邻。
由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
G1的内存模型:
说明:
G1是物理分区,逻辑分代
- 红色区域是年轻代( young generation),包含伊甸区(eden regions,红色不带S区域)和幸存区(survivor regions ,红色带S区域)
- 浅蓝色区域是老年代(old generation),其中包含跨多个区域组成的大对象区域(Humongous Region,蓝色带H区域)
- 灰色区域表示空闲区(free regions)
传统的GC内存模型:
传统的垃圾回收器把内存分成三类: Eden(E), Suvivor(S)、Old(O)和持久代。 Eden(E), Suvivor(S)属于年轻代,Old(O)属于老年代,且各区域的内存空间是连续的。
针对G1的内存模型中的补充说明:
- 巨型对象Humongous Region
一个大小达到甚至超过分区大小一半的对象称为巨型对象(Humongous Object)
。当线程为巨型对象分配空间时,不能简单在TLAB进行分配,因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。因此,巨型对象会直接在老年代分配
,所占用的连续空间称为巨型分区(Humongous Region)
。
G1内部做了一个优化,一旦发现没有引用指向巨型对象,则可直接在年轻代收集周期中被回收。
巨型对象会独占一个、或多个连续分区
,其中第一个分区被标记为开始巨型(Starts Humongous)
,相邻连续分区被标记为连续巨型(Continues Humongous)
。由于无法享受Lab带来的优化,并且确定一片连续的内存空间需要扫描整堆,因此确定巨型对象开始位置的成本非常高,如果可以,应用程序应避免生成巨型对象。
对于巨型对象,有以下几个点需要注意:
-
没有被引用的巨型对象会在标记清理阶段或者Full GC时被释放掉。
-
Young GC和Mixed GC阶段都会对巨型对象进行回收
-
巨型对象永远不会移动,即使在Full GC中
-
每一个region中都只有一个巨型对象,该region剩余的部分得不到利用,会导致堆碎片化。
-
如果看到由于大对象分配导致频繁的并发回收,需要把大对象变为普通的对象,建议增大Region size(或者切换到ZGC)。但是增大Region size有一个负面影响就是:减少了可用region的数量。因此,对于这种情况,你需要进行相应的测试,以查看是否实际提高了应用程序的吞吐量或延迟。
-
在分配巨型对象之前会先检查是否超过 initiating heap occupancy percent和the marking threshold, 如果超过的话,就启动global concurrent marking,为的是提早回收,防止 evacuation failures 和 Full GC。
-
记忆集合Remember Set (RSet)
在串行和并行收集器中,GC通过整堆扫描,来确定对象是否处于可达路径中。然而G1为了避免STW式的整堆扫描,在每个分区记录了一个已记忆集合(RSet)
,内部类似一个反向指针,记录引用分区内对象的卡片索引。当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况。事实上,并非所有的引用都需要记录在RSet中,如果一个分区确定需要扫描,那么无需RSet也可以无遗漏的得到引用关系。那么引用源自本分区的对象,当然不用落入RSet中;同时,G1 GC每次都会对年轻代进行整体收集,因此引用源自年轻代的对象,也不需要在RSet中记录。最后只有老年代的分区可能会有RSet记录,这些分区称为拥有RSet分区(an RSet’s owning region)。
注意⚠️:每个区域Region都有一个记忆集RSet,列出了从外部指向该区域的引用。RSet中的信息是实时维护的,也就是每次产生外部引用都会立刻记录到RSet中,而不需要等待GC时才产生。
-
收集集合 (CSet)
收集集合(CSet)代表每次GC暂停时回收的一系列目标分区。在任意一次收集暂停中,CSet所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。因此无论是年轻代收集,还是混合收集,工作的机制都是一致的。年轻代收集CSet只容纳年轻代分区,而混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到CSet中。候选老年代分区的CSet准入条件,可以通过活跃度阈值-XX:G1MixedGCLiveThresholdPercent(默认85%)进行设置,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据CSet对堆的总大小占比-XX:G1OldCSetRegionThresholdPercent(默认10%)设置数量上限。由上述可知,G1的收集都是根据CSet进行操作的,年轻代收集与混合收集没有明显的不同,最大的区别在于两种收集的触发条件
五、G1的标记过程
当堆的整体占用足够大时,并发标记开始。默认情况下,它是45%,通过参数InitiatingHeapOccupancyPercent控制。
并发标记(Concurrent Marking)阶段主要是为Mixed GC做准备
G1的标记分为以下几个阶段:
1、初始标记 Initial marking phase
此阶段标志着从GC根直接到达的所有对象,该阶段依赖于年轻代垃圾收集(young gc),该阶段是STW
的。由于GC Roots数量不多,通常该阶段耗时非常短。事实上,当达到IHOP阈值时,G1并不会立即发起并发标记周期,而是等待下一次年轻代收集,利用年轻代收集的STW时间段,完成初始标记,这种方式称为借道(Piggybacking)
。
1.631: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0062656 secs]
2、根区域扫描阶段 Root region scanning phase
扫描在初始标记阶段被标识的幸存区域,标记那些被老年代引用的存活对象。此阶段与应用程序(而非 STW)并发运行,必须在下一个 STW 年轻垃圾回收开始之前完成。
在初始标记暂停结束后,年轻代收集也完成了对象复制到Survivor的工作,应用线程开始活跃起来。此时为了保证标记算法的正确性,所有新复制到Survivor分区的对象,都需要被扫描并标记成根,这个过程称为根分区扫描(Root Region Scanning)
,同时扫描的Suvivor分区也被称为根分区(Root Region)
。根分区扫描必须在下一次年轻代垃圾收集启动前完成(并发标记的过程中,可能会被若干次年轻代垃圾收集打断),因为每次GC会产生新的存活对象集合。
1.362: [GC concurrent-root-region-scan-start]
1.364: [GC concurrent-root-region-scan-end, 0.0028513 secs]
3、并发标记阶段 Concurrent marking phase
G1 GC 在整个堆中查找可访问的(活动的)对象。这个阶段与应用程序同时发生,并且可以被 STW 年轻的垃圾回收中断。
1.364: [GC concurrent-mark-start]
1.645: [GC concurrent-mark-end, 0.2803470 secs]
4、重标记阶段 Remark phase
重新标记(Remark)是最后一个标记阶段。在该阶段中,G1需要一个暂停的时间STW
,去处理剩下的SATB日志缓冲区和所有更新,找出所有未被访问的存活对象,同时安全完成存活数据计算。这个阶段也是并行执行的,通过参数-XX:ParallelGCThread可设置GC暂停时可用的GC线程数。同时,引用处理也是重新标记阶段的一部分,所有重度使用引用对象(弱引用、软引用、虚引用、最终引用)的应用都会在引用处理上产生开销,最后还会执行一些类卸载操作。
1.645: [GC remark 1.645: [Finalize Marking, 0.0009461 secs] 1.646: [GC ref-proc, 0.0000417 secs] 1.646: [Unloading, 0.0011301 secs], 0.0074056 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
5、清除阶段 Cleanup phase
紧挨着重新标记阶段的清除(Clean)阶段,该阶段是部分并发的。
清除阶段主要执行以下操作:
- RSet梳理,启发式算法会根据活跃度和RSet尺寸对分区定义不同等级,同时RSet数理也有助于发现无用的引用。参数
-XX:+PrintAdaptiveSizePolicy
可以开启打印启发式算法决策细节; - 整理堆分区,为混合收集周期识别回收收益高(基于释放空间和暂停目标)的老年代分区集合;
- 识别所有空闲分区,即发现无存活对象的分区。这类分区可在清除阶段直接回收,无需等待下次收集周期。由于这样区域没有存活对象,所以采用并发清空回收。
1.652: [GC cleanup 1213M->1213M(1885M), 0.0030492 secs]
[Times: user=0.01 sys=0.00, real=0.00 secs]
如果需要回收一部分没有存活对象的区域,则日志如下:
1.872: [GC cleanup 1357M->173M(1996M), 0.0015664 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
1.874: [GC concurrent-cleanup-start]
1.876: [GC concurrent-cleanup-end, 0.0014846 secs]
注意:
- 在标记过程的最后一个阶段:清除阶段 Cleanup phase,会直接对没有存活对象的分区进行回收,无需等待下次收集周期。
- 在初始标记阶段、重标记阶段、清理阶段会出现STW,根区域扫描阶段和并发标记阶段都可以和应用程序并发执行,不会出现STW。
作为对比,CMS的老年代回收采用的是标记-清除算法
,其标记过程如下:
- 初始标记(CMS Initial Mark) —— 标记GC root能直接关联的对象(短暂STW)
- 并发标记(CMS Concurrent Mark)—— GCRootsTracing,从并发标记中的root遍历,对不可达的对象进行标记
- 重新标记(CMS Remark)—— 修正并发标记期间因为用户操作导致标记发生表更的对象,采用的incremental update算法,会出现比较多的STW
- 并发清除(CMS Concurrent Sweep)—— 由于是直接清理,不涉及对象的复制转移,所以阶段可以并发执行。
小结:
CMS在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿,只有初始标记和重新标记会出现STW
。
这样看好像比G1老年代的标记-复制算法
暂停时间更少(因为G1的清理阶段也会出现STW暂停)。其实不然:
1、G1的初始标记阶段是在Young GC的STW过程中同步完成的。
3、重新标记阶段,当堆内存太大时,CMS重新标记的STW时间会逐渐不可控,而G1的重新标记利用RSet采用的SATB算法STW时间非常短暂。
2、CMS会对老年代全区域进行回收,而G1采用预测性算法对老年代Region的回收性价比排序,每次都是在保证暂停时间可控的情况下回收性价比最高的内存Region,所以单次回收的STW时间更加可控。
六、G1的垃圾回收
G1提供了两种GC模式,Young GC和Mixed GC,两种都是完全Stop The World的。
1、G1过程梳理
- 应用程序启动时,会首先向服务器申请分配JVM内存,并将申请到的内存划分为许多大小相同的区域Region,这时的区域都是空闲状态 (Free regions)。
- 当应用程序开始运行后,会持续产生新的对象,G1内存管理器会分配空闲(Free regions)作为年轻代的伊甸区(eden regions)存放这些新产生的对象。如果新产生的对象大于Region的一半,则直接放入老年区的大对象区域(Humongous Region)。
- G1 GC为了匹配软实时(soft real-time)的目标会动态调整年轻代的大小,当年轻代被填满后,就会触发Young GC,Young GC会对整个年轻代和大对象区域(Humongous Region)进行回收。Young GC结束后依然存活的对象,会被疏散evacuation到n(n>=1)个新的Survivor分区,或者是老年代。
- 当java heap占用达到 InitiatingHeapOccupancyPercent 定义的阈值之后,在下一个Young GC开始的时候,同时开始进行并发标记(Concurrent Marking)。
- 并发标记(Concurrent Marking)和Young GC穿插执行,在Concurrent Marking的过程中可能会出现多次Young-only GC。
- 在并发标记(Concurrent Marking)的清理阶段,会直接回收无存活对象的分区。
- 当并发标记(Concurrent Marking)结束后,会根据-XX:G1HeapWastePercent=5设置的阈值判断是否需要执行Mixed GC。
- 在Mixed GC阶段,会对所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region进行回收。
- 如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用Serial old GC(full GC)来收集整个java堆空间。注意G1本身并不提供full GC的。
G1中的垃圾回收主要分为2类:Young GC和Mixed GC
说明:
1、图中的圆圈表示G1回收过程中的暂停:蓝色圆圈表示Young-only GC导致的暂停,红色圆圈表示Mixed GC导致的暂停,黄色圆圈表示有并发标记导致的暂停。
2、当java heap占用达到 InitiatingHeapOccupancyPercent 定义的阈值之后,下一个Young-only GC收集将也会进行并发标记的初始标记,如图中大蓝色圆圈。
3、Young-only GC和Concurrent Marking阶段可以穿插执行,在Concurrent Marking的过程中可能会出现多次Young-only GC,而Mixed GC只能在Concurrent Marking阶段完成后才能执行。
4、当完成并发标记阶段后,不一定会立刻进行Mixed GC,也可能会进行几次Young-only GC后才会进行Mixed GC。(可能并没有达到G1HeapWastePercent设置的阈值)
5、蓝色圆圈数量多于红色圆圈数量,表示一般情况下,Young-only GC发生的次数往往要大于Mixed GC的次数,这也是G1努力使垃圾回收更加高效。
2、Young GC
年轻代垃圾回收阶段,该阶段也被称为Young-only或Fully Young阶段,会对整个年轻代的区域Region进行回收。
Eden区耗尽的时候就会触发新生代收集,新生代垃圾收集会对整个新生代(E + S)进行回收。
- 新生代垃圾收集期间,整个应用STW
- 新生代垃圾收集是由多线程并发执行的
- 通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。
- 新生代收集结束后依然存活的对象,会被疏散evacuation到n(n>=1)个新的Survivor分区,或者是老年代。
- 该阶段会进行大对象区域的回收
Young GC日志示例:
该次Young GC暂停过程中,同时进行了大对象的分配,并完成了并行标记的初始化标记。
2023-02-10T09:43:54.663+0800: 10690912.762: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0274320 secs][Parallel Time: 21.8 ms, GC Workers: 4][GC Worker Start (ms): Min: 10690912762.4, Avg: 10690912762.4, Max: 10690912762.6, Diff: 0.2][Ext Root Scanning (ms): Min: 3.2, Avg: 3.5, Max: 3.7, Diff: 0.5, Sum: 14.1][Update RS (ms): Min: 16.6, Avg: 16.7, Max: 17.1, Diff: 0.5, Sum: 66.9][Processed Buffers: Min: 211, Avg: 216.8, Max: 225, Diff: 14, Sum: 867][Scan RS (ms): Min: 0.1, Avg: 0.3, Max: 0.5, Diff: 0.4, Sum: 1.4][Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Object Copy (ms): Min: 0.5, Avg: 0.7, Max: 0.9, Diff: 0.4, Sum: 2.9][Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1][Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4][GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1][GC Worker Total (ms): Min: 21.3, Avg: 21.4, Max: 21.4, Diff: 0.1, Sum: 85.6][GC Worker End (ms): Min: 10690912783.8, Avg: 10690912783.8, Max: 10690912783.8, Diff: 0.0][Code Root Fixup: 0.0 ms][Code Root Purge: 0.0 ms][String Dedup Fixup: 2.4 ms, GC Workers: 4][Queue Fixup (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Table Fixup (ms): Min: 2.1, Avg: 2.2, Max: 2.3, Diff: 0.3, Sum: 8.9][Clear CT: 0.3 ms][Other: 2.9 ms][Choose CSet: 0.0 ms][Ref Proc: 0.7 ms][Ref Enq: 0.0 ms][Redirty Cards: 0.1 ms][Humongous Register: 0.1 ms][Humongous Reclaim: 0.6 ms][Free CSet: 0.9 ms][Eden: 2170.0M(2454.0M)->0.0B(2452.0M) Survivors: 2048.0K->4096.0K Heap: 3693.3M(4096.0M)->946.1M(4096.0M)][Times: user=0.10 sys=0.00, real=0.03 secs]
3、Mixed GC
混合垃圾回收阶段,该阶段也被称为Space-reclamation阶段,会选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region进行回收。
- 整个阶段都是STW
- 所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region进行回收
- Mixed GC不是Full GC,它只能回收部分老年代的Region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用Serial old GC(full GC)来收集整个GC heap。G1本身并不提供full GC的。
- 该阶段会进行大对象区域的回收
2023-02-10T09:07:24.661+0800: 10688722.759: [GC pause (G1 Evacuation Pause) (mixed), 0.0105385 secs][Parallel Time: 7.1 ms, GC Workers: 4][GC Worker Start (ms): Min: 10688722759.3, Avg: 10688722759.3, Max: 10688722759.4, Diff: 0.0][Ext Root Scanning (ms): Min: 1.6, Avg: 2.0, Max: 2.6, Diff: 1.1, Sum: 7.9][Update RS (ms): Min: 3.9, Avg: 4.4, Max: 4.6, Diff: 0.7, Sum: 17.4][Processed Buffers: Min: 48, Avg: 52.8, Max: 56, Diff: 8, Sum: 211][Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.4][Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Object Copy (ms): Min: 0.5, Avg: 0.6, Max: 0.7, Diff: 0.3, Sum: 2.3][Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4][GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][GC Worker Total (ms): Min: 7.0, Avg: 7.0, Max: 7.1, Diff: 0.0, Sum: 28.2][GC Worker End (ms): Min: 10688722766.4, Avg: 10688722766.4, Max: 10688722766.4, Diff: 0.0][Code Root Fixup: 0.0 ms][Code Root Purge: 0.0 ms][String Dedup Fixup: 2.3 ms, GC Workers: 4][Queue Fixup (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0][Table Fixup (ms): Min: 2.2, Avg: 2.2, Max: 2.2, Diff: 0.0, Sum: 8.8][Clear CT: 0.1 ms][Other: 1.0 ms][Choose CSet: 0.0 ms][Ref Proc: 0.3 ms][Ref Enq: 0.0 ms][Redirty Cards: 0.1 ms][Humongous Register: 0.1 ms][Humongous Reclaim: 0.1 ms][Free CSet: 0.2 ms][Eden: 204.0M(204.0M)->0.0B(2454.0M) Survivors: 0.0B->2048.0K Heap: 1255.0M(4096.0M)->944.2M(4096.0M)][Times: user=0.04 sys=0.00, real=0.01 secs]
4、Full GC
作为一种兜底的备份策略,如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。
- 整个过程都是STW
- G1并不提供full GC的,这个Serial old GC提供的。Full gc是单线程的(在Java 8中)并且非常慢,因此应避免在G1 GC的时候出现这个Full GC
不能觉得用了G1 GC收集器之后,Java heap里面的GC不是Young GC 就Mixed GC,还是存在Full GC。
G1 GC一旦发生了Full GC,就说明当前程序的运行可能出现问题的,需要考虑为什么会Full GC了?
G1是如何满足目标暂停时间的?
前提:G1的JVM内存模型:在物理上分区region、逻辑上分代
- 在年轻代收集(Young GC)期间,G1 GC 调整年轻代(伊甸园和幸存者)的大小以来匹配软实时(soft real-time)的目标。
- 在混合收集(Mixed GC)期间,G1 GC 根据混合垃圾收集的目标数量、堆中每个区域中活动对象的百分比以及总体可接受的堆浪费百分比来调整收集的旧区域的数量,以满足目标暂停时间目标。
七、参数介绍
参数 | 说明 |
---|---|
-XX:+UseG1GC | 使用 G1 收集器 |
-XX:G1HeapRegionSize=n | 设置 G1区域Region的大小。范围从1 MB 到32MB之间,目标是根据最小的 Java 堆大小划分出大约2048个区域。 |
-XX:MaxGCPauseMillis=200 | 设置最长暂停时间目标值,默认是200毫秒 |
-XX:G1NewSizePercent=5 | 设置年轻代最小值占总堆的百分比,默认值是5% |
-XX:G1MaxNewSizePercent=60 | 设置年轻代最大值占总堆的百分比,默认值是java堆的60% |
-XX:ParallelGCThreads=n | 设置STW并行工作的GC线程数,一般推荐设置该值为逻辑处理器的数量,最大是8;如果逻辑处理器大于8,则取逻辑处理器数量的5/8;这适用于大多数情况,除非是较大的SPARC系统,其中的n值可以是逻辑处理器的5/16 |
-XX:ConcGCThreads=n | 并发标记阶段,并发执行的线程数,一般n值为并行垃圾回收线程数(ParallelGCThreads)的1/4左右 |
-XX:InitiatingHeapOccupancyPercent=45 | 设置触发全局并发标记周期的Java堆内存占用率阈值,默认占用率阈值是整个Java堆的45% |
-XX:G1MixedGCLiveThresholdPercent=85 | 老年代Region中存活对象的占比,只有当占比小于此参数的Old Region,才会被选入CSet。这个值越大,说明允许回收的Region中的存活对象越多,可回收的空间就越少,gc效果就越不明显 |
-XX:G1HeapWastePercent=5 | 设置G1中愿意浪费的堆的百分比,如果可回收region的占比小于该值,G1不会启动Mixed GC,默认值10%,主要用来控制Mixed GC的触发时机。在global concurrent marking结束之后,我们可以知道老年代regions中有多少空间要被回收,在每次YGC之后和再次Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会触发Mixed GC。 |
-XX:G1MixedGCCountTarget=8 | 一次全局并发标记后,最多执行Mixed GC的次数,次数越多,单次回收的老年代的Region个数就越少,暂停也就越短 |
-XX:G1OldCSetRegionThresholdPercent=10 | 一次Mixed GC过程中老年代Region内存最多能被选入CSet中的占比 |
-XX:G1ReservePercent=10 | 设置作为空闲空间的预留内存百分比,用来降低目标空间溢出的风险,默认是10%,一般增加或减少百分比时,需要确保也对java堆调整相同的量。 |
如何解锁VM经验值标志参数?
在Java8中G1的众多参数中,包含3个经验值参数,如果需要调整经验值参数的值,需要先解锁经验值标志。
3个经验值:
-XX:G1NewSizePercent=5
-XX:G1MaxNewSizePercent=60
-XX:G1MixedGCLiveThresholdPercent=85
实例:添加UnlockExperimentalVMOptions参数,并调整参数G1NewSizePercent参数的经验值
java -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=10 -XX:G1MaxNewSizePercent=75 G1test.jar
注意事项:
- 避免通过-Xmn 或其他相关选项(如-XX: NewRate)显式设置年轻代的大小,因为年轻代的大小被固定后会导致G1的目标暂停机制失效。
- 当设置目标暂停时间
MaxGCPauseMillis
时,需要评估G1 GC 的延迟和应用程序吞吐量之间的取舍,当该值设置较小时,表明您愿意承担垃圾收集开销的增加,从而会导致应用程序吞吐量的降低。 - Mixed GC的调优:
1)-XX:InitiatingHeapOccupancyPercent
: 控制并发标记开始的内存占比阈值
2)-XX:G1HeapWastePercent
: 设置G1中愿意浪费的堆的百分比,如果可回收region的占比小于该值,G1不会启动Mixed GC,默认值10%,主要用来控制Mixed GC的触发时机。
3)-XX:G1MixedGCLiveThresholdPercent
:设置老年代Region进入CSet的活跃对象占比阈值,避免活跃对象占比过高的Region进入CSet
3)-XX:G1MixedGCCountTarget
and-XX:G1OldCSetRegionThresholdPercent
: 主要是为了控制单次Mixed GC中Region的个数,CSet中Region的个数越多,GC过程中暂停时间越长。
八、典型问题
1、疏散失败(Evacuation Failure)
当没有更多的空闲region被提升到老一代或者复制到幸存空间时,并且由于堆已经达到最大值,堆不能扩展,从而发生Evacuation Failure,这时G1 的GC已经无能为力,只能使用通过Serial old GC进行Full GC来收集整个java堆空间,这个过程就是转移失败(Evacuation Failure)。
Young GC 疏散暂停(Evacuation Pause)过程出现内存耗尽的对应日志:
注意:G1 Evacuation Pause
指的是G1垃圾回收过程中存活对象的复制-转移阶段,被称为‘疏散暂停’阶段。
解决方案:
- 如果有大量“空间耗尽(to-space exhausted)”或“空间溢出(to-space overflow)”GC事件,则增加-XX:G1ReservePercent以增加“to-space”的预留内存量,默认值是Java堆的10%。注意:G1 GC将此值限制在50%以内。
- 通过减少 -XX: InitiatingHeapOccupancyPercent 的值来更早地启动并发标记周期,来及时回收不包含活跃对象的区域,同时促使Mixed GC更快发生。
- 增加选项-XX:ConcGCThreads的值以增加并行标记线程的数量,减少并行标记阶段的耗时。
2、大对象分配(Humongous Allocation)
Young GC过程中大对象分配时出现内存耗尽的对应日志:
原因分析:
出现大对象分配导致的内存耗尽问题,一般是老年代剩余的Region中已经不能够找到一组连续的区域分配给新的巨型对象。
解决方案:
- 通过-XX: G1HeapRegionSize 选项来增加内存区域Region的大小,提升Region对象的判断标准,以减少巨大对象的数量。
- 增加堆java的大小使得有更多的空间来存放巨型对象
- 通过-XX:G1MaxNewSizePercent降低年轻代Region的占比,给老年代预留更多的空间,从而给巨型对象提供给多的内存空间。
一般在疏散阶段(Evacuation Pause)和大对象分配(Humongous Allocation)会比较容易出现“空间耗尽(to-space exhausted)”或“空间溢出(to-space overflow)”的GC事件,导致出现转移失败(Evacuation Failure) ,进而引发Full GC 从而导致GC的暂停时间超过G1的设置的目标暂停时间。
所以我们要尽量避免出现转移失败(Evacuation Failure)。
3、Young GC花费时间太长
通常Young GC的耗时与年轻代的大小成正比,具体地说,是需要复制的集合集中的活跃对象的数量。
如果Young GC中CSet的疏散阶段(Evacuate Collection Set phase)需要很长时间,尤其是其中的对象复制-转移,可以通过降低-XX:G1NewSizePercent
的值,降低年轻代的最小尺寸,从而降低停顿时间。
还可以使用-XX:G1MaxNewSizePercent
降低年轻代的最大占比,从而减少Young GC暂停期间需要处理的对象数量。
4、Mixed GC耗时太长
- 通过降低
-XX:InitiatingHeapOccupancyPercent
的值,来调低并发标记阶段开始的阈值,让并发标记阶段更早触发,只有并发标记完成才能开始执行Mixed GC。 - 通过调节
-XX:G1MixedGCCountTarget
和-XX:G1OldCSetRegionThresholdPercent
参数,降低单次回收的Region数量,减少暂停时间。 - 通过调节
-XX:G1MixedGCLiveThresholdPercent
的值,避免活跃对象占比过高的Region进入CSet。因为活的对象越多,region中可回收的空间就越少,暂停时间就越长,gc效果就越不明显。 - 通过调节
-XX:G1HeapWastePercent
的值,设置愿意浪费的堆的百分比。只有垃圾占比大于此参数,才会发生Mixed GC,该值越小,会越早触发Mixed GC。
总结
本来主要对G1垃圾回收器的相关特性和实现机制做了详细介绍。
1、首先G1的内存模型进行了说明:物理上分区,逻辑上分代,年轻代和老年代的区域不是连续的内存空间,而是由分散的大小相同的内存块Region组成,G1 GC会通过动态条件年轻代区域Region的数量以来匹配软实时(soft real-time)的目标。
2、详解介绍了G1 GC中的完整的垃圾回收流程以及Young GC、并发标记和Mixed GC的实现细节。
3、介绍了G1 GC的优缺点以及选用场景。
4、对G1的核心参数做了相关介绍,并针对G1的重点问题提供了参数优化建议。
相关文章:
G1垃圾回收器详解
文章目录前言一、思考问题二、官方文档三、基本介绍四、G1的内存模型五、G1的标记过程六、G1的垃圾回收1、G1过程梳理2、Young GC3、Mixed GC4、Full GC七、参数介绍八、典型问题1、疏散失败(Evacuation Failure)2、大对象分配(Humongous All…...
tws耳机哪个牌子音质好?tws耳机音质排行榜
随着蓝牙耳机市场的不断发展,使用蓝牙耳机的人也逐渐增多,近年来更是超越有线耳机成为最火爆的数码产品之一。那么,tws耳机哪个牌子音质好?下面,我来给大家推荐几款音质好的tws耳机,可以当个参考。 一、南…...
TIA博途中DB数据块清零的具体方法示例
TIA博途中DB数据块清零的具体方法示例 TIA中数据块如何实现清零? 在TIA指令集内有多个移动指令可对DB块内数据进行清零处理。对于S7-1500 CPU或ET200SP CPU来说,可使用BLKMOV、FILL以及SCL的POKE_BLK指令。但是这些指令对DB块清零时,要求DB块必需为非优化DB。 对于优化的DB…...
iptables防火墙屏蔽指定ip的端口
因为需要测试客户端程序与hadoop服务器之间正常通信需要开通的端口, 所以在hadoop各服务器上使用iptables防火墙屏蔽了测试客户端程序的ip和所有端口。然后,根据报错信息提示的端口号来逐步放开直到能正常通信下载文件。 在服务器端屏蔽指定ip访问所有端口 #查看…...
JavaScript Math(算数) 对象
JavaScript Math(算数) 对象 Math 是一个内置对象,它拥有一些数学常数属性和数学函数方法。Math 不是一个函数对象。 Math 用于 Number 类型。它不支持 BigInt。 描述 与其他全局对象不同的是,Math 不是一个构造器。Math 的所…...
超详细的JAVA高级进阶基础知识04
目录 4. 面向对象高级 - 常用的API 4.1 Arrays 工具类 4.1.1 Arrays 类介绍 4.2 冒泡排序 4.3 选择排序 4.4 二分查找 4.5 正则表达式 4.5.1 String 类中与正则有关的常见方法 4.5.2 练习 4.5.3 今日学习目标 4. 面向对象高级 - 常用的API 4.1 Arrays 工具类 4.1.1…...
Python 运算符?
什么是运算符? 本章节主要说明Python的运算符。举个简单的例子 4 5 9 。 例子中,4 和 5 被称为操作数, 称为运算符。 Python语言支持以下类型的运算符: 算术运算符 比较(关系)运算符 赋值运算符 逻辑运算符 位运算符…...
linux nuxt 部署 问题汇总
安装node centos 7 请选择 node版本 v16.1.0 进行安装, npm 版本 7.11.2 亲测这个版本有效,否则会出现 Error: Cannot find module ‘node:fs/promises‘ 等问题 node安装亲参照: Linux node 安装教程_linux node安装_围城少年的博客…...
C++内存管理
文章目录1. c的内存管理例题2.c管理方式1.c的内置类型1.申请一个空间并初始化2.申请连续的空间并初始化3.总结2.c的自定义类型2.总结3.operator new与operator delete函数4.new和delete的实现原理1.内置类型2.自定义类型内存泄露问题&&delete先析构的原因编译器实现机制…...
电子招投标系统源码之 —采购数字化转型快人一步,以大数据支撑供应链管理未来
采购数字化转型快人一步,以大数据支撑供应链管理未来 招标采购为主的一站式全流程数字化采供协同。平台满足询比价、招标、竞价、拍卖、协议直采、商城采购等多种采购定价方式,采购过程全程留痕可追溯。平台支持企业通过WEB、APP、小程序等终端完成采购需…...
ie获取cookie数据,中文乱码;cookie中文乱码终极解决办法
终极解决办法 cookie存中文数据是会出现乱码的,所以在存数据前,得先“编码”,取的时候先“解码” JS方法-编码:encodeURI("你好") 结果:"%E4%BD%A0%E5%A5%BD"JS方法-解码:decodeURI(&…...
day16_关键字this和super丶就近原则和追根溯源原则
this关键字 含义:this代表当前对象 this使用位置 this在实例初始化相关的代码块和构造器中:表示正在创建的那个实例对象,即正在new谁,this就代表谁this在非静态实例方法中:表示调用该方法的对象,即谁在调…...
MySQL 共享锁 (lock in share mode),排他锁 (for update)
共享锁 (lock in share mode) 简介 允许不同事务之间加共享锁读取,但不允许其它事务修改或者加入排他锁 如果有修改必须等待一个事务提交完成,才可以执行,容易出现死锁 共享锁事务之间的读取 session1: start transaction; select * from…...
类与对象(下)
文章目录类与对象(下)1. 再谈构造函数1.1 构造函数体赋值1.2 初始化列表特点推荐坑1.3 explicit关键字2. static成员2.1 概念面试题解法1解法2解法3(重要)2.2 特性问题3. 友元3.1 友元函数说明3.2 友元类4. 内部类4.14.25. 匿名对象6.拷贝对象时的一些编译器优化总结类与对象(下…...
feign技巧 - form方式传值
feign技巧 - form方式传值。 0. 文章目录1. 前言2. 调用样例3. 原理解析3.1 feign端序列化参数3.2 SpringMVC服务端解析参数3.3 补充 - 继承关系不会被传递的原因3.4 补充 - 不能使用GET。4. 总结1. 前言 直接正题。 如何使用feign进行fom表单方式的请求调用,以及其…...
MATLAB | 情人节来绘制更立体的玫瑰花吧
又是一年情人节,今年带来一款更有立体感的玫瑰: 曲面的函数表达式来自: http://www.bugman123.com/Math/index.html 这个网站,上面还有很多其他帅气的玩意。 基础绘制 xlinspace(0,1,300); thetalinspace(-2*pi,15*pi,300); [x,theta]meshg…...
【Python表白代码】 2.14“Valentine‘s Day”“没别的意思 就是借着特殊日子说声喜欢你”你在哪儿?我去见你~(各种玫瑰源码合集)
导语 Valentines Day Every man is a poet when he is in love 所有文章完整的素材源码都在👇👇 粉丝白嫖源码福利,请移步至CSDN社区或文末公众hao即可免费。 哈喽!我是你们的木木子吖~ 情人节又到了,礼物备好了没&am…...
压力应变电桥信号隔离放大变送器差分输入0-±10mV/0-±20mV转0-20mA/0-10v
概述:DIN11 IPO 压力应变桥信号处理系列隔离放大器是一种将差分输入信号隔离放大、转换成按比例输出的直流信号导轨安装变送模块。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等行业。此系列模块内部嵌入了一个高效微功率的电源,向输入…...
Linux系统之部署个人导航页
Linux系统之部署个人导航页 一、本次导航页工具介绍二、检查本地系统环境1.检查系统版本2.检查系统内核版本三、下载导航页软件包1.创建下载目录2.下载导航页软件包四、部署前环境准备工作1.安装python32.安装pipenv3.创建虚拟环境①创建环境②修改base.py文件③修改settings.p…...
四、Windows 平台安装 MongoDB
MongoDB 提供 64 位系统的预编译二进制包 我们可以从 MongoDB 官网下载安装 MongoDB 预编译二进制包下载地址:Try MongoDB Atlas Products | MongoDB 在 MongoDB 2.2 版本后已经不再支持 Windows XP 系统 最新版本也已经没有了 32 位系统的安装文件 MongoDB for W…...
浅谈应用安全测试工具
正确的应用程序安全测试工具可以改善企业安全态势和开发工作流程。如今,应用程序安全从一开始就内置在整个软件生命周期中,即使是具有成熟开发实践的组织也需要自动化工具来在复杂、快速变化的环境中成功地保护他们的软件。以下比较了三个广泛使用的应用…...
四类(七种)排序算法总结
一、插入排序 基本思想: 每次将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。即边插入边排序,保证子序列中随时都是排好序的。 基本操作——有序插入ÿ…...
[oeasy]python0083_十进制数如何存入计算机_八卦纪事_BCD编码_Binary_Coded_Decimal
编码进化 回忆上次内容 上次 研究了 视频终端的 演化 从VT05 到 VT100从 黑底绿字 到 RGB 24位真彩色形成了 VT100选项 从而 将颜色 数字化 了 生活中我们更常用 10个数字 但是 计算机中 用二进制 日常计数的十进制数 是如何存储进计算机的呢?🤔 从10进制到2进…...
理解框架的编译时与运行时
首先我们需要先理解一下什么事编译时和运行时 在语言层面,先来聊一下前端开发者最常遇见的两种语言JavaScript和Java Java的代码就是被编译为.class 文件才能运行,这个编译过程就是编译时,运行 .class 文件就是运行时我们在浏览器直接输入一…...
推挽电路---采用二极管消除交越失真----克服交越失真的互补推挽输出电路图
交越失真产生的原因及消除方法 由于晶体管的门限电压不为零,比如一般的硅三极管,NPN型在0.7V以上才导通,这样在00.7就存在死区,不能完全模拟出输入信号波形,PNP型小于-0.7V才导通,比如当输入的交流的正弦波…...
day11_面向对象
今日内容 零、 复习昨日 一、一日一题(数组,OOP) 二、面向对象练习(方法参数返回值) 三、局部变量&成员变量 四、this关键字 五、构造方法 六、重载 七、封装 小破站同步上课视频: https://space.bilibili.com/402601570/channel/collectiondetail?…...
大数据处理学习笔记1.1 搭建Scala开发环境
文章目录零、本讲学习目标一、Scala简介(一)Scala概述(二)函数式编程(三)Scala特性1、一切都是对象2、一切都是函数3、一切都是表达式(四)在线运行Scala二、选择Scala版本三、Window…...
VSCODE C++ 调用matplotlibcpp画图
使用VSCODE编写C程序,想在调试过程中看中间数据的波形,于是找到了python的matplotlibcpp库,参考文章链接是:https://blog.csdn.net/weixin_43769166/article/details/118365416;按照他的步骤配置好之后,跳出…...
面对“开门红”,跨境支付如何寻求新增长曲线?
易观:2022年是第三方支付行业洗牌加剧的一年,在部分机构选择退出的过程中,也有机构开始瞄准跨境业务,成为了支付机构转型的重要方向之一。跨境支付是指两个或及其以上的国家或地区进行国际贸易、国际投资或其他经济活动࿰…...
MySQL入门篇-MySQL MHA高可用实战
MHA简介 MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司的youshimaton(现就职于Facebook公司)开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提…...
哪个网站做h5好/电子商务说白了就是干什么的
基于微信小程序的食堂订餐点餐管理系统项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架管理前端使用vue,用户使用微信小程序的食堂订餐点餐小程序共分为三个角色:系统管理员、商家、用户 管理员角色包含以下功能: 首页展示、…...
有没有教做生态手工的网站/成都网站优化平台
在我们设计过程中,经常需要和客户交换文件,此时就会涉及到不同格式文件的转换,如何保证我们输出和输入的文件和原始数据尽可能保持一致就变得尤为重要了,一次好的输出和输入能够减少我们大量的修改和整理时间。在Creo3.0及以前版本…...
烟台优化公司/优化网站seo公司
众所周知,计算机底层的数据都是0和1. 那么我们在输入数字的时候,要交给计算机处理,首先要转化成计算机能识别的0和1的形式。那么文字是怎么样转化成0和1的呢? 通过字符集。常用的字符集是ASCII,每个字母每个符号都对应…...
教做鞋的网站/软文营销的概念
myecplise上将工程部署到应用下时,经常出现 An internal error occurred during: "Add Deployment". java.lang.NullPointException 错误。很让人郁闷。 究其原因,一般是因为项目从别处导入的,从cvs上down下来的等。 原因一、jdk版…...
建设企业功能网站/网络推广主要是做什么工作
转自 Laravel 社区:https://laravel-china.org/top...Laravel Dusk 当编写接口测试时,Laravel 提供了一组有用的帮助方法,用来方便地单击链接,填充表单文件或提交表单。Laravel 使用 Symfony BrowserKit 组件来模拟 Web 浏览器的行…...
网站建设费用推荐网络专业/哈尔滨seo推广优化
线性表的查找操作:线性表l查找第一个与元素e满足compare()元素的位置。若在,返回其在l中的次序。若不存在则输出不存在 。 int locateelem_sq(sqlist *l,elemtype e,status (*compare)(elemtype, elemtype)) 第一步设i1,从线性表第一个位置开…...