JVM——垃圾回收


垃圾回收

  1. 如何判断对象可以回收

  2. 垃圾回收算法

  3. 分代垃圾回收

  4. 垃圾回收器

  5. 垃圾回收调优

 

1. 如何判断对象可以回收

1.1 引用计数法

 

引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;

1.2 可达性分析算法

  • Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象

  • 扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收(Root对象`:不能被当成垃圾回收的对象)

    可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

  • 哪些对象可以作为 GC Root ?

1.3 四种引用

  1. 强引用

    • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。通过引用链找不到的,都会被回收。

  2. 软引用(SoftReference)

    • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象

    • 可以配合引用队列来释放软引用自身

  3. 弱引用(WeakReference)

    • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象

    • 可以配合引用队列来释放弱引用自身

  4. 虚引用(PhantomReference)

    • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存

  5. 终结器引用(FinalReference)

    • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象

2. 垃圾回收算法

2.1 标记清除

定义: Mark Sweep

  • 速度较快

  • 会造成内存碎片 

标记无用对象,然后进行清除回收。

标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:

  • 标记阶段:标记出可以回收的对象。

  • 清除阶段:回收被标记的对象所占用的空间。

标记-清除算法之所以是基础的,是因为后面讲到的垃圾收集算法都是在此算法的基础上进行改进的。

优点:实现简单,不需要对象进行移动。

缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。

2.2 标记整理

定义:Mark Compact

  • 速度慢

  • 没有内存碎片 

在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。

优点:解决了标记-清理算法存在的内存碎片问题。

缺点:仍需要进行局部对象移动,一定程度上降低了效率。

2.3 复制

定义:Copy

  • 不会有内存碎片

  • 需要占用双倍内存空间 

 

为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。

优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。

缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。

3. 分代垃圾回收

 

  • 对象首先分配在伊甸园区域

  • 新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from to

  • minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行

  • 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)

  • 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时间更长

3.1 相关 VM 参数

含义参数
堆初始大小 -Xms
堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例 -XX:SurvivorRatio=ratio
晋升阈值 -XX:MaxTenuringThreshold=threshold
晋升详情 -XX:+PrintTenuringDistribution
GC详情 -XX:+PrintGCDetails -verbose:gc
FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC

 

3.2.举例:

A:方法中无内容

B:加入7M

C:加入2*512k

4. 垃圾回收器

  1. 串行

    • 单线程

    • 堆内存较小,适合个人电脑

  2. 吞吐量优先

    • 多线程

    • 堆内存较大,多核 cpu

    • 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高

  3. 响应时间优先

    • 多线程

    • 堆内存较大,多核 cpu

    • 尽可能让单次 STW 的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5

       

    Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。

场景:吞吐量优先

解答:吞吐量大,一般需要一个大的年轻代和小的老年代,因为吞吐量大,就要求存的少,那么年轻代就会稍大容易回收,老年代稍小,只存放长期对象。

场景:响应时间优先

解答:响应时间快,gc次数就要少,尽量增加年轻代内存,直到接近系统时间响应的限制;年老代设置比较麻烦,如果设置过大,会造成gc收集时间长,如果设置过小,会造成gc次数过多,需要考虑几个因素:1.持久代并发收集次数、2.花在年轻代和年老代的时间比例、3.gc信息


在实践中我们发现对于大多数的应用领域,评估一个垃圾收集(GC)算法如何根据如下两个标准:

 吞吐量越高算法越好
 暂停时间越短算法越好

首先让我们来明确垃圾收集(GC)中的两个术语:吞吐量(throughput)和暂停时间(pause times)。 JVM在专门的线程(GC threads)中执行GC。 只要GC线程是活动的,它们将与应用程序线程(application threads)争用当前可用CPU的时钟周期。 简单点来说,吞吐量是指应用程序线程用时占程序总用时的比例。 例如,吞吐量99/100意味着100秒的程序执行时间应用程序线程运行了99秒, 而在这一时间段内GC线程只运行了1秒。

术语”暂停时间”是指一个时间段内应用程序线程让与GC线程执行而完全暂停。 例如,GC期间100毫秒的暂停时间意味着在这100毫秒期间内没有应用程序线程是活动的。 如果说一个正在运行的应用程序有100毫秒的“平均暂停时间”,那么就是说该应用程序所有的暂停时间平均长度为100毫秒。 同样,100毫秒的“最大暂停时间”是指该应用程序所有的暂停时间最大不超过100毫秒。 吞吐量 VS 暂停时间

高吞吐量最好因为这会让应用程序的最终用户感觉只有应用程序线程在做“生产性”工作。 直觉上,吞吐量越高程序运行越快。 低暂停时间最好因为从最终用户的角度来看不管是GC还是其他原因导致一个应用被挂起始终是不好的。 这取决于应用程序的类型,有时候甚至短暂的200毫秒暂停都可能打断终端用户体验。 因此,具有低的最大暂停时间是非常重要的,特别是对于一个交互式应用程序。

不幸的是”高吞吐量”和”低暂停时间”是一对相互竞争的目标(矛盾)。这样想想看,为了清晰起见简化一下:GC需要一定的前提条件以便安全地运行。 例如,必须保证应用程序线程在GC线程试图确定哪些对象仍然被引用和哪些没有被引用的时候不修改对象的状态。 为此,应用程序在GC期间必须停止(或者仅在GC的特定阶段,这取决于所使用的算法)。 然而这会增加额外的线程调度开销:直接开销是上下文切换,间接开销是因为缓存的影响。 加上JVM内部安全措施的开销,这意味着GC及随之而来的不可忽略的开销,将增加GC线程执行实际工作的时间。 因此我们可以通过尽可能少运行GC来最大化吞吐量,例如,只有在不可避免的时候进行GC,来节省所有与它相关的开销。

然而,仅仅偶尔运行GC意味着每当GC运行时将有许多工作要做,因为在此期间积累在堆中的对象数量很高。 单个GC需要花更多时间来完成, 从而导致更高的平均和最大暂停时间。 因此,考虑到低暂停时间,最好频繁地运行GC以便更快速地完成。 这反过来又增加了开销并导致吞吐量下降,我们又回到了起点。 综上所述,在设计(或使用)GC算法时??,我们必须确定我们的目标:一个GC算法??只可能针对两个目标之一(即只专注于最大吞吐量或最小暂停时间),或尝试找到一个二者的折衷。

原文链接:https://blog.csdn.net/dcm19920115/java/article/details/90708458


 

4.1 串行

-XX:+UseSerialGC = Serial(发生在新生代,复制算法) + SerialOld(发生在老年代,标记整理算法)

所有线程必须在安全点停下来。当一个线程触发垃圾回收机制的时候,就不会对其他线程产生影响。回收完毕后,线程再执行。

4.2 吞吐量优先

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC (新生代GC,复制算法;OldGC,标记整理算法)

-XX:+UseAdaptiveSizePolicy(自适应调整调整,会根据GC的情况自动计算计算 Eden、From 和 To 区的大小;)

-XX:GCTimeRatio=ratio

-XX:MaxGCPauseMillis=ms (default time is 200ms.if time become long,the GC 's time will long ,too.)

-XX:ParallelGCThreads=n (controler threads number)

当四个线程都到达安全点后,一起进行GC,此时CPU占用率会非常高。

1.-XX:+UseParallelGC ~ -XX:+UseParallelOldGC

 -XX:+UseParallelGC
 
 有了这个标志,我们告诉JVM使用多线程并行执行年轻代垃圾收集。 在我看来,Java 6中不应该使用该标志因为-XX:+UseParallelOldGC显然更合适。 需要注意的是Java 7中该情况改变了一点(详见本概述),就是-XX:+UseParallelGC能达到-XX:+UseParallelOldGC一样的效果。
 
 -XX:+UseParallelOldGC
 
 该标志的命名有点不巧,因为”老”听起来像”过时”。 然而,”老”实际上是指年老代,这也解释了为什么-XX:+UseParallelOldGC要优于-XX:+UseParallelGC:除了激活年轻代并行垃圾收集,也激活了年老代并行垃圾收集。 当期望高吞吐量,并且JVM有两个或更多可用处理器核心时,我建议使用该标志。
 作为旁注,HotSpot的并行面向吞吐量垃圾收集算法通常称为”吞吐量收集器”,因为它们旨在通过并行执行来提高吞吐量。

2.JVM参数之UseAdaptiveSizePolicy

2.1、AdaptiveSizePolicy(自适应大小策略) :

     JDK 1.8 默认使用 UseParallelGC 垃圾回收器,该垃圾回收器默认启动了 AdaptiveSizePolicy,会根据GC的情况自动计算计算 Eden、From 和 To 区的大小;

2.2、配置:

   开启:-XX:+UseAdaptiveSizePolicy
   关闭:-XX:-UseAdaptiveSizePolicy

2.3、注意事项:

      3.1、在 JDK 1.8 中,如果使用 CMS,无论 UseAdaptiveSizePolicy 如何设置,都会将 UseAdaptiveSizePolicy 设置为 false;不过不同版本的JDK存在差异;
      3.2、UseAdaptiveSizePolicy不要和SurvivorRatio参数显示设置搭配使用,一起使用会导致参数失效;
      3.3、由于AdaptiveSizePolicy会动态调整 Eden、Survivor 的大小,有些情况存在Survivor 被自动调为很小,比如十几MB甚至几MB的可能,这个时候YGC回收掉 Eden区后,还存活的对象进入Survivor 装不下,就会直接晋升到老年代,导致老年代占用空间逐渐增加,从而触发FULL GC,如果一次FULL GC的耗时很长(比如到达几百毫秒),那么在要求高响应的系统就是不可取的。

附:对于面向外部的大流量、低延迟系统,不建议启用此参数,建议关闭该参数。 3.-XX:GCTimeRatio

通过-XX:GCTimeRatio=<value>我们告诉JVM吞吐量要达到的目标值。 更准确地说,-XX:GCTimeRatio=N指定目标应用程序线程的执行时间(与总的程序执行时间)达到N/(N+1)的目标比值。 例如,通过-XX:GCTimeRatio=9我们要求应用程序线程在整个执行时间中至少9/10是活动的(因此,GC线程占用其余1/10)。 基于运行时的测量,JVM将会尝试修改堆和GC设置以期达到目标吞吐量。 -XX:GCTimeRatio的默认值是99,也就是说,应用程序线程应该运行至少99%的总执行时间。

4.-XX:MaxGCPauseMillis

通过-XX:GCTimeRatio=<value>告诉JVM最大暂停时间的目标值(以毫秒为单位)。 在运行时,吞吐量收集器计算在暂停期间观察到的统计数据(加权平均和标准偏差)。 如果统计表明正在经历的暂停其时间存在超过目标值的风险时,JVM会修改堆和GC设置以降低它们。 需要注意的是,年轻代和年老代垃圾收集的统计数据是分开计算的,还要注意,默认情况下,最大暂停时间没有被设置。 如果最大暂停时间和最小吞吐量同时设置了目标值,实现最大暂停时间目标具有更高的优先级。 当然,无法保证JVM将一定能达到任一目标,即使它会努力去做。 最后,一切都取决于手头应用程序的行为。 当设置最大暂停时间目标时,我们应注意不要选择太小的值。 正如我们现在所知道的,为了保持低暂停时间,JVM需要增加GC次数,那样可能会严重影响可达到的吞吐量。 这就是为什么对于要求低暂停时间作为主要目标的应用程序(大多数是Web应用程序),我会建议不要使用吞吐量收集器,而是选择CMS收集器。 CMS收集器是本系列下一部分的主题。

5.-XX:ParallelGCThreads

通过-XX:ParallelGCThreads=<value>我们可以指定并行垃圾收集的线程数量。 例如,-XX:ParallelGCThreads=6表示每次并行垃圾收集将有6个线程执行。 如果不明确设置该标志,虚拟机将使用基于可用(虚拟)处理器数量计算的默认值。 决定因素是由Java Runtime。availableProcessors()方法的返回值N,如果N<=8,并行垃圾收集器将使用N个垃圾收集线程,如果N>8个可用处理器,垃圾收集线程数量应为3+5N/8。 当JVM独占地使用系统和处理器时使用默认设置更有意义。 但是,如果有多个JVM(或其他耗CPU的系统)在同一台机器上运行,我们应该使用-XX:ParallelGCThreads来减少垃圾收集线程数到一个适当的值。 例如,如果4个以服务器方式运行的JVM同时跑在在一个具有16核处理器的机器上,设置-XX:ParallelGCThreads=4是明智的,它能使不同JVM的垃圾收集器不会相互干扰。

4.3 响应时间优先

-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld

-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads

-XX:CMSInitiatingOccupancyFraction=percent

-XX:+CMSScavengeBeforeRemark (重新标记之前,对新生代做垃圾回收)

-XX:+UseParNewGC:

设置年轻代为并发收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。 CMS, 全称Concurrent Low Pause Collector,是jdk1.4后期版本开始引入的新gc算法,在jdk5和jdk6中得到了进一步改进,它的主要适合场景是对响应时间的重要性需求 大于对吞吐量的要求,能够承受垃圾回收线程和应用线程共享处理器资源,并且应用中存在比较多的长生命周期的对象的应用。CMS是用于对tenured generation的回收,也就是年老代的回收,目标是尽量减少应用的暂停时间,减少FullGC发生的几率,利用和应用程序线程并发的垃圾回收线程来 标记清除年老代。 -XX:+UseConcMarkSweepGC:

设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了。所以,此时年轻代大小最好用-Xmn设置。

-XX:CMSInitiatingOccupancyFraction=70:

表示年老代空间到70%时就开始执行CMS,确保年老代有足够的空间接纳来自年轻代的对象。 注:如果使用 throughput collector 和 concurrent low pause collector 这两种垃圾收集器,需要适当的挺高内存大小,为多线程做准备。

4.4 G1

定义:Garbage First

  • 2004 论文发布

  • 2009 JDK 6u14 体验

  • 2012 JDK 7u4 官方支持

  • 2017 JDK 9 默认

适用场景

  • 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms

  • 超大堆内存,会将堆划分为多个大小相等的 Region

  • 整体上是标记+整理算法,两个区域之间是复制算法

相关 JVM 参数

-XX:+UseG1GC

-XX:G1HeapRegionSize=size

-XX:MaxGCPauseMillis=time

 

 

1) G1 垃圾回收阶段

 

 

2) Young Collection

  • 会 STW

 

 

 

 

 

 

 

 

 

 

3) Young Collection + CM

  • 在 Young GC 时会进行 GC Root 的初始标记

  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定

-XX:InitiatingHeapOccupancyPercent=percent (默认45%)

 

 

 

 

4) Mixed Collection

会对 E、S、O 进行全面垃圾回收

  • 最终标记(Remark)会 STW

  • 拷贝存活(Evacuation)会 STW

-XX:MaxGCPauseMillis=ms

 

5) Full GC

  • SerialGC

    • 新生代内存不足发生的垃圾收集 - minor gc

    • 老年代内存不足发生的垃圾收集 - full gc

  • ParallelGC

    • 新生代内存不足发生的垃圾收集 - minor gc

    • 老年代内存不足发生的垃圾收集 - full gc

  • CMS

    • 新生代内存不足发生的垃圾收集 - minor gc

    • 老年代内存不足

  • G1

    • 新生代内存不足发生的垃圾收集 - minor gc

    • 老年代内存不足

 

 

6) Young Collection 跨代引用

  • 新生代回收的跨代引用(老年代引用新生代)问题

 

  • 卡表与 Remembered Set

  • 在引用变更时通过 post-write barrier + dirty card queue

  • concurrent refinement threads 更新 Remembered Set

 

 

7) Remark

  • pre-write barrier + satb_mark_queue

 

 

8) JDK 8u20 字符串去重

  • 优点:节省大量内存

  • 缺点:略微多占用了 cpu 时间,新生代回收时间略微增加

-XX:+UseStringDeduplication

 String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
 String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
  • 将所有新分配的字符串放入一个队列

  • 当新生代回收时,G1并发检查是否有字符串重复

  • 如果它们值一样,让它们引用同一个 char[]

  • 注意,与 String.intern() 不一样

    • String.intern() 关注的是字符串对象

    • 而字符串去重关注的是 char[]

    • 在 JVM 内部,使用了不同的字符串表

 

 

9) JDK 8u40 并发标记类卸载

所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类 -XX:+ClassUnloadingWithConcurrentMark 默认启用

 

10) JDK 8u60 回收巨型对象

  • 一个对象大于 region 的一半时,称之为巨型对象

  • G1 不会对巨型对象进行拷贝

  • 回收时被优先考虑

  • G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生代垃圾回收时处理掉

     

11) JDK 9 并发标记起始时间的调整

  • 并发标记必须在堆空间占满前完成,否则退化为 FullGC

  • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent

  • JDK 9 可以动态调整

    • -XX:InitiatingHeapOccupancyPercent 用来设置初始值

    • 进行数据采样并动态调整

    • 总会添加一个安全的空档空间

 

12) JDK 9 更高效的回收

 

5. 垃圾回收调优

预备知识

  • 掌握 GC 相关的 VM 参数,会基本的空间调整

  • 掌握相关工具(jmap、jconsole、Java visualvm)

  • 明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则

 

掌握 GC 相关的 VM 参数

1.Oracle documents for JVM commands:

https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE

2.GC 相关的 VM 参数

5.1 调优领域

  • 内存

  • 锁竞争

  • cpu 占用

  • io

5.2 确定目标

  • 【低延迟】还是【高吞吐量】,选择合适的回收器

  • 低延迟:CMS,G1,ZGC

  • ParallelGC

  • Zing(零停顿)

5.3 最快的 GC

答案是不发生 GC

  • 查看 FullGC 前后的内存占用,考虑下面几个问题

    • 数据是不是太多?

      • resultSet = statement.executeQuery("select * from 大表 limit n")

    • 数据表示是否太臃肿?

      • 对象图

      • 对象大小 :object最小占16 byte; Integer 24; int 4(根据情况选择适当的数据类型)

    • 是否存在内存泄漏?

      • static Map map = (错误方式)

      • 第三方缓存实现

 

5.4 新生代调优

  • 新生代的特点

    • 所有的 new 操作的内存分配非常廉价

      • TLAB thread-local allocation buffer

    • 死亡对象的回收代价是零

    • 大部分对象用过即死

    • Minor GC 的时间远远低于 Full GC

  • 越大越好吗?

-Xmn Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). GC is performed in this region more often than in other regions. If the size for the young generation is too small, then a lot of minor garbage collections are performed. If the size is too large, then only full garbage collections are performed, which can take a long time to complete. Oracle recommends that you keep the size for the young generation greater than 25% and less than 50% of the overall heap size.

1/4 heap size < young generation< 1/2 heap size

 

理想情况:

  • 新生代能容纳所有【并发量 * (请求-响应)】的数据

  • 幸存区大到能保留【当前活跃对象+需要晋升对象】

  • 晋升阈值配置得当,让长时间存活对象尽快晋升

    -XX:MaxTenuringThreshold=threshold(调整晋升阈值)
    
    -XX:+PrintTenuringDistribution(print log)
    
     Desired survivor size 48286924 bytes, new threshold 10 (max 10)
     - age 1: 28992024 bytes, 28992024 total
     - age 2: 1366864 bytes, 30358888 total(1+2- age 3: 1425912 bytes, 31784800 total(1+2+3)
     ...

5.5 老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好

  • 先尝试不做调优,如果没有 Full GC 那么已经...,否则先尝试调优新生代

  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3

    • -XX:CMSInitiatingOccupancyFraction=percent (值尽量小,一有垃圾就回收)

 

 

5.6 案例

  • 案例1 Full GC 和 Minor GC频繁

  • 案例2 请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)

  • 案例3 老年代充裕情况下,发生 Full GC (CMS jdk1.7)

 

 

案例1 Full GC 和 Minor GC频繁 ?

分析:Minor GC频繁 ,说明幸存区空间小。很多对象晋升为老年代,触发Full GC。导致连锁反应。

解决方法:增加新生代的空间,增加幸存区的大小,可以使新生代有足够的对象。同时提升对象晋升的阈值,减缓对象升为老年代,尽量减少生命周期较短的对象留在新生代,不晋升为老年代。同时可以减少Full GC的发生。

 

案例2 请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)

分析:时间长发生在重新标记阶段,所以需要在重新标记之前进行清理。

解决方法:-XX:+CMSScavengeBeforeRemark (重新标记之前,对新生代做垃圾回收)

 

案例3 老年代充裕情况下,发生 Full GC (CMS jdk1.7)

分析:永久代空间设置的太小

解决方法:增加永久代的空间

 

 

原文地址:https://www.cnblogs.com/aaaazzzz/p/13326456.html