JAVA8 标准版HotSpot虚拟机GC调优

串行收集器Serial基于行为的调整

Java SE提供了两个垃圾收集调整目标:

  • 最大暂停时间目标STW(stop the word),暂停是指由于正在进行垃圾回收而导致应用程序无响应的时间。

  • 应用程序吞吐量目标,吞吐量是长时间内未用于垃圾回收的总时间的百分比。

    请注意,这些行为不确保一定会满足。该应用程序需要足够大的堆,以至少容纳所有实时数据。此外,最小堆大小可能会阻止达到这些期望的目标。

最大暂停时间目标

暂停时间是垃圾收集器停止应用程序并恢复不再使用的空间的持续时间。最大暂停时间目标的目的是限制这些暂停中的最长时间。垃圾回收器会保持平均的停顿时间和该平均值的方差。平均值是从执行开始时获取的,但经过加权后,最近的暂停次数会增加。如果平均时间加上暂停时间的方差大于最大暂停时间目标,则垃圾回收器认为未达到目标。

最大暂停时间目标是通过命令行选项指定的-XX:MaxGCPauseMillis=<nnn>毫秒或更短。垃圾收集器将调整Java堆大小以及与垃圾收集相关的其他参数,以使垃圾收集暂停时间短于<nnn>毫秒。默认情况下,没有最大暂停时间目标。这些调整可能导致垃圾回收器更频繁地发生,从而降低了应用程序的整体吞吐量。垃圾收集器会尝试在吞吐量目标之前达到任何暂停时间目标。但是,在某些情况下,无法达到所需的暂停时间目标。

吞吐量目标

吞吐量目标是根据收集垃圾所花费的时间和垃圾收集之外所花费的时间(称为应用时间)来衡量的。目标由命令行选项指定-XX:GCTimeRatio=<nnn>。垃圾收集时间与应用程序时间的比率为1 /(1 + <nnn>)。

例如,-XX:GCTimeRatio=19将垃圾收集目标设置为目标1/20或总时间的5%。垃圾收集所花费的时间是年轻一代和老一代收集的总时间。如果没有达到吞吐量目标,那么将增加当前代的大小,以增加应用程序在集合之间运行的时间。

堆最小目标

如果已满足吞吐量和最大暂停时间目标,则垃圾收集器将减小堆的大小,直到无法满足其中一个目标(始终是吞吐量目标)。然后解决未实现的目标。

调整策略

除非您知道需要的堆大于默认的最大堆大小,否则不要为堆选择最大值。选择适合您的应用程序的吞吐量目标。堆将增长或缩小到可以支持所选吞吐量目标的大小。应用程序行为的更改可能导致堆增大或缩小。例如,如果应用程序开始以更高的速率分配,堆将增长以保持相同的吞吐量。如果堆增长到最大大小,并且无法满足吞吐量目标,则最大堆大小对于吞吐量目标而言太小。将最大堆大小设置为接近平台上总物理内存但不会导致应用程序交换的值。再次执行该应用程序。如果仍然不能满足吞吐量目标,则对于平台上的可用内存,应用程序时间目标太高。如果可以达到吞吐量目标,但暂停时间太长,则选择最大暂停时间目标。如果选择最大暂停时间目标可能意味着您的吞吐量目标将无法实现,因此请选择对应用程序可接受的折衷值。通常,随着垃圾收集器试图满足竞争目标,堆的大小会振荡。即使应用程序已达到稳定状态,也是如此。实现吞吐量目标(可能需要更大的堆)的压力与目标竞争,以获得最大的暂停时间和最小的占用空间(这两者都可能需要小的堆)。

优化策略

内存要分代管理(存储着不同年龄对象的内存池)。当堆完全使用完成时,垃圾回收主要GC会在每个代中发生。绝大多数对象分配在专用于年轻对象(年轻代)的池中,并且大多数对象在那里死亡。当年轻代填满时,会导致次要GC触发收集年轻代;不回收其它代的垃圾。假设年轻代中的大多数对象都是垃圾并且可以回收,则可以优化次要GC。

 

测量

吞吐量和占用空间最好使用特定于应用程序的指标来衡量。例如,可以使用客户端负载生成器来测试Web服务器的吞吐量。

 

命令行选项-verbose:gc使有关堆和垃圾收集的信息在每个收集处打印。

例如,以下是大型服务器应用程序的输出:

[GC 325407K-> 83000K(776768K),0.2300771秒]

[GC 325816K-> 83372K(776768K),0.2454258秒]

[FGC 267628K-> 83769K(776768K),1.8479984秒]

上面的信息表示输出两个次要GC,然后是一个主要GC。箭头之前和之后的数字(例如,325407K->83000K)分别表示垃圾回收之前和之后的活动对象的组合大小。在进行次要GC之后,该大小包括一些暂时无法回收的对象,表示仍然有引用存在。括号中的下一个数字(例如,(776768K)【包括幸存者空间】)是堆的已回收大小,也就是Java对象可用的空间量,而无需从操作系统请求更多的内存。除了在垃圾回收期间,在任何给定时间仅将使用一个幸存空间来存储对象。最后一项(例如0.2300771 secs)表示执行收集花费的时间,在这种情况下约为四分之一秒。 注意:-verbose:gc可能会在将来的版本中更改。

 

命令行选项-XX:+PrintGCDetails导致有关GC其他信息被打印。-XX:+PrintGCDetails此处显示了使用串行垃圾收集器的输出示例。

[GC [DefNew:64575K-> 959K(64576K),0.0457646秒] 196016K-> 133633K(261184K),0.0459067秒]

DefNew: 64575K->959K(64576K)表示DefNew(年轻代)的收集恢复了约98%的空间,耗时0.0457646 secs约45毫秒。

196016K->133633K(261184K)表示整个堆的使用减少到了约51%,最终总开销为0.0459067 secs

注意:-XX:+PrintGCDetails可能会在将来的版本中更改。

 

命令行选项-XX:+PrintGCTimeStamps在每个收集开始时添加一个时间戳。

这对于查看垃圾收集发生的频率很有用。

111.042:[GC 111.042:[DefNew:8128K-> 8128K(8128K),0.0000505秒] 111.042:[Tenured:18154K-> 2311K(24576K),0.1290354秒] 26282K-> 2311K(32704K),0.1293306秒]

收集开始到应用程序执行大约111秒。次要GC大约在同一时间开始。此外,还显示了Tenured(老年代-养老区)所描绘的主要回收信息。长期使用的使用率降低到约10%(18154K->2311K(24576K)),并花费了0.1290354 secs大约130毫秒的时间。

 

总堆

  • 影响垃圾收集性能的最重要因素是总可用内存

  • 收集垃圾发生内存快用尽时,因此与可用内存量成反比

参数=默认值描述
-XX:MinHeapFreeRatio=40某代区域维持比例,当空间下降到40%以下,则将代空间扩容致该比例
-XX:MaxHeapFreeRatio=70某代区域维持比例,当空间超过70%以上,则将代空间自动缩小致该比例
-Xms=6656k总堆最小值
-Xmx=calculated总堆保留最大值,例如,如果您的计算机具有128 MB的物理内存,则最大堆大小为64 MB,并且大于或等于1 GB的物理内存将导致最大堆大小为256 MB
-XX:NewRatio=2年轻代相对老年代的占比,例如,设置-XX:NewRatio=3意味着年轻代和老年代之间的比率为1:3。换句话说,伊甸园空间和幸存者空间的总大小将是堆总大小的四分之一
-XX:NewSize=1310M代替NewRatio,使用控制更加精确的年轻代最小空间
-XX:MaxNewSize=不限代替NewRatio,使用控制更加精确的年轻代最大空间
-XX:SurvivorRatio=8例如,-XX:SurvivorRatio=6将伊甸园和幸存者空间之间的比例设置为1:6。换句话说,每个幸存者空间将是伊甸园大小的六分之一,因此是年轻代的八分之一(而不是七分之一,因为有两个幸存者空间)

年轻代,包含幸存区

  • 在总可用内存之后,影响垃圾收集性能的第二大影响因素是专用于年轻代的堆的比例

    但是,对于有限的堆大小,较大的年轻代意味着较小的老年代,这将增加FGC的频率。最佳选择取决于应用程序分配的对象的生命周期分布。

幸存空间

  • 参数SurvivorRatio来调整幸存空间的大小,但这对于性能通常并不重要

    如果幸存者空间太小,复制大对象或者集合时会直接溢出到老年代。如果幸存者空间太大,又太浪费空间。在每次垃圾回收时,虚拟机都会选择一个阈值数,该阈值数在回收之前可以复制的次数。命令行选项-XX:+PrintTenuringDistribution(并非在所有垃圾收集器上都可用)可用于显示此阈值和新一代对象的寿命。这对于观察应用程序的生命周期分布也很有用。

优化原则

  • 首先确定您可以负担得起的虚拟机的最大堆大小。然后针对年轻代绘制性能指标,以找到最佳设置。

    • 请注意,最大堆大小应始终小于计算机上安装的内存量,以避免过多的页面错误和崩溃。

  • 如果总堆大小是固定的,老年代相对稳定时,则增加年轻代大小。使使用空间足够大,以容纳应用程序在任何给定时间使用的所有实时数据,以及一定数量的闲置空间(10%到20%或更多)

    • 随着处理器数量的增加,可以增加年轻代的大小,因为分配可以并行化。

 

串行与并行收集器的区别和应用场景

  • 串行收集器使用单个线程来执行所有垃圾收集工作,这使之相对高效,因为线程之间没有通信开销。它最适合单处理器计算机,因为它不能利用多处理器硬件,尽管它在多处理器上对于数据集较小(大约100 MB)的应用很有用。默认情况下,在某些硬件和操作系统配置上选择了串行收集器,或者可以使用选项显式启用串行收集器-XX:+UseSerialGC

  • 并行收集器(也称为吞吐量收集器)并行执行GC,这可以大大减少垃圾收集的开销。它适用于具有在多处理器或多线程硬件上运行的中型到大型数据集的应用程序。默认情况下,在某些硬件和操作系统配置上选择了并行收集器,或者可以使用选项显式启用并行收集器-XX:+UseParallelGC

    • 并行压缩是使并行收集器能够并行执行主要收集的功能。如果没有并行压缩,则使用单个线程执行主要GC,这会大大限制可伸缩性。如果-XX:+UseParallelGC指定了该选项,则默认情况下启用并行压缩。将其关闭的选项是-XX:-UseParallelOldGC

    • 如果峰值应用程序性能是第一要务,并且没有暂停时间要求或可接受的暂停时间为1秒或更长时间,则使用命令-XX:+UseParallelGC来选择并行收集器。

    • 在许多数据大小和硬件组合上,并行收集器的暂停时间将超过1秒。

  • 大多数并发的收集器会同时执行其大部分工作(例如,在应用程序仍在运行时),以使垃圾收集暂停时间较短。它设计用于具有中型到大型数据集的应用程序,在这些应用程序中,响应时间比整体吞吐量更重要,因为用于最小化暂停的技术会降低应用程序性能。

    • 使用-XX:+UseConcMarkSweepGC启用CMS收集器或-XX:+UseG1GC启用G1收集器。

    • 如果响应时间比整体吞吐量更重要,并且垃圾收集暂停时间必须保持小于1秒,那么请使用-XX:+UseConcMarkSweepGC或选择并发收集器-XX:+UseG1GC

 

并行收集器

参数默认值描述
-XX:+UseParallelGC+jdk8默认收集器,默认情况下,使用此选项,次要和主要收集都将并行执行,以进一步减少垃圾收集的开销。默认同时开始UseParallelOldGC
-XX:ParallelGCThreads>8:5/8 <8: n并发线程数
-XX:-UseParallelOldGC+如果UseParallelGC开启,则默认开启
-XX:MaxGCPauseMillis0毫秒最大垃圾回收暂停时间,默认情况下没有暂停时间,如果设置了,则JVM会自动调整其它参数来尽可能的满足这个设置值
-XX:GCTimeRatio99吞吐量:吞吐量目标是根据进行垃圾收集所花费的时间与在垃圾收集之外所花费的时间(称为应用程序时间)来衡量的。该目标由命令行选项指定,该选项-XX:GCTimeRatio=<N>将垃圾回收时间与应用程序时间之比设置为1 / (1 + N)。例如,-XX:GCTimeRatio=19将垃圾收集的目标设置为总时间的1/20或5%。默认值为99,导致垃圾回收的目标时间为1%。
-Xmxcalculated最大堆使用选项指定-Xmx=<n>。另外,收集器还有一个隐含的目标,即只要满足其他目标,就将堆的大小最小化。。

 

目标优先

按照以下顺序解决目标:

  • 最大暂停时间目标

  • 吞吐量目标

  • 最小堆空间目标

    首先达到最大暂停时间目标。只有在达到目标之后,才能实现吞吐量目标。同样,只有在达到前两个目标后,才会考虑足迹目标。

堆中各代大小的自动调整和设置优化

  • 增长和缩小代的大小是通过增加作为代大小的固定百分比来完成的,以便使代朝其期望的大小递增或递减。生长和收缩以不同的速率进行。默认情况下,一代以20%的增量增长,而以5%的增量缩小。成长百分比由-XX:YoungGenerationSizeIncrement=<Y>年轻代和-XX:TenuredGenerationSizeIncrement=<T>老年代的命令行选项控制。代缩小的百分比由命令行标志调整-XX:AdaptiveSizeDecrementScaleFactor=<D>。如果增长增量为X为百分比,则收缩的减少量为X / D百分比。

  • 如果收集器决定在启动时增加代,则将按照上一条的设置增加一次对应的比例。并使之随着收集的数量而衰减,并且没有长期影响。增加的目的是为提高启动性能。缩小百分比没有补充。

  • 如果没有达到最大暂停时间目标,则一次仅缩小一代的大小。如果两个世代的暂停时间都超过了目标,则首先缩减具有较大暂停时间的世代的大小。

  • 如果未达到吞吐量目标,则两代的空间都会增加。每一个都按其对总垃圾收集时间的贡献成比例地增加。例如,如果年轻代的垃圾收集时间为总收集时间的25%,且如果年轻代的全部增量为20%,则年轻代将增加5%。

  • 你可以设置-Xms-Xmx相同的值。否则,JVM将使用初始堆大小开始,然后根据需要增大Java堆,直到找到堆使用和性能之间的平衡为止。

  • 其他参数和选项可能会影响这些默认值。要验证您的默认值

    java -XX:+ PrintFlagsFinal -版本 | grep MaxHeapSize

  • 并行收集器会OutOfMemoryError在垃圾回收(GC)上花费过多的时间:如果在垃圾回收中花费了总时间的98%以上,而回收不到2%的堆,则OutOfMemoryError抛出。此功能旨在防止应用程序长时间运行,而由于堆太小而几乎没有进展,甚至没有进展。如有必要,可以通过-XX:-UseGCOverheadLimit在命令行中添加选项来禁用此功能。

并发开销

  • 大多数并发的收集器以处理器资源(否则应用程序可以使用)为代价,以缩短主要的GC暂停时间。最明显的开销是在集合的并发部分使用一个或多个处理器。在N个处理器系统上,集合的并发部分将使用可用处理器的K / N,其中1 <= K <= ceiling { N / 4}。(请注意,对K的精确选择和范围可能会发生变化。)除了在并发阶段使用处理器外,还会产生额外的开销来实现并发。因此,虽然并发收集器的GC暂停通常要短得多,但应用程序吞吐量也往往比其他收集器要低一些。

测量

  • 并行收集器输出的详细垃圾收集器与串行收集器的输出基本相同

 

并发标记扫描(CMS)收集器

此收集器用于那些希望更短的垃圾收集暂停并能够与垃圾收集共享处理器资源的应用程序。

CMS收集器通过命令行选项启用-XX:+UseConcMarkSweepGC

CMS收集器是分代处理。因此,次要GC和主要GC都会发生。CMS收集器尝试通过使用单独的垃圾收集器线程在执行应用程序线程的同时使用三色标记法并行跟踪可访问对象,来减少由于主要GC而导致的暂停时间。在每个主要GC周期中,CMS收集器仍会暂停所有应用程序线程一小段时间

CMS主要问题之一 并发模式故障

CMS收集器使用一个或多个垃圾收集器线程,这些垃圾收集器线程与应用程序线程同时运行,目的是在使用期限生成完成之前完成其收集。如前所述,在正常操作中,CMS收集器在应用程序线程仍在运行的情况下执行其大部分跟踪和清除工作,因此应用程序线程仅会看到短暂的暂停。但是,如果CMS收集器无法在内存填满之前完成对无法访问的对象的标记和回收,或者无法增量生成可用空间块,则无法满足分配要求,会暂停应用程序,并使所有应用程序线程停止。无法同时完成收集的过程称为并发模式失败,表示需要调整CMS收集器参数。如果并发收集被显式垃圾收集(System.gc())中断,或者需要为诊断工具提供信息的垃圾收集中断,则将报告并发模式中断。

另外三色标记还会存在并发问题,就是会存在漏标,导致无法正常回收该部分内存,并最终导致OOM

  • CMS收集器将OutOfMemoryError在垃圾收集上花费太多时间:如果在垃圾收集中花费了总时间的98%以上,而回收不到2%的堆,则OutOfMemoryError抛出。此功能旨在防止应用程序长时间运行,而由于堆太小而几乎没有进展,甚至没有进展。如有必要,可以通过-XX:-UseGCOverheadLimit在命令行中添加选项来禁用此功能。该策略与并行收集器中的策略相同,除了执行并发收集所花费的时间不计入98%的时间限制。换句话说,只有在应用程序停止时执行的收集才计入过多的GC时间。此类收集通常是由于并发模式故障或显式收集请求(例如对的调用System.gc)引起的。

  • 暂停 CMS收集器在并发收集周期中两次暂停应用程序。第一个暂停是将可从根直接访问的对象(例如,来自应用程序线程堆栈和寄存器的对象引用,静态对象等)和从堆中其他位置(例如,年轻代)直接标记为活动状态。此第一个停顿称为初始标记停顿。第二个暂停是在并发跟踪阶段的末尾,并查找由于CMS收集器完成对对象的引用后,应用程序线程对对象中的引用进行了更新而导致并发跟踪遗漏的对象。第二个暂停称为备注暂停

  • 浮动垃圾 与Java HotSpot VM中的所有其他收集器一样,CMS收集器是一个跟踪收集器,它至少标识堆中的所有可访问对象。在Richard Jones和Rafael D. Lins的出版物《垃圾收集:自动动态内存算法》中,它是一个增量更新收集器。由于应用程序线程和垃圾收集器线程在主收集期间同时运行,因此垃圾收集器线程跟踪的对象随后可能会在收集过程结束时变得不可访问。尚未回收的此类无法访问的对象称为浮动垃圾。漂浮垃圾量取决于并发收集周期的持续时间以及应用程序更新参考更新的频率(也称为突变)。此外,由于年轻代和终身代是独立收集的,因此每个人都是彼此的根源。作为粗略的指导,尝试将永久代的大小增加20%,以解决浮动垃圾的问题。在一个并发收集周期结束时,将在下一个收集周期中收集堆中的浮动垃圾。

  • 并发阶段 可达对象的并发跟踪发生在初始标记暂停和注释暂停之间。在此并发跟踪阶段,一个或多个并发垃圾收集器线程可能正在使用处理器资源。结果,即使没有暂停应用程序线程,在此阶段和其他并发阶段中,受计算绑定的应用程序吞吐量也可能会相应下降。备注暂停后,并发清除阶段将收集标识为不可访问的对象。

  • 开始并发收集周期 并发收集的开始必须定时,以使收集可以在终身代用完之前完成。否则,由于并发模式故障,将导致应用程序更长时间的暂停。有几种启动并发收集的方法。根据最近的历史记录,CMS收集器将保留对永久性代用尽之前的剩余时间以及并发收集周期所需时间的估计。使用这些动态估计,开始并发的收集周期,目的是在内存用尽之前完成并发收集周期。为了安全起见,对这些估计值进行了补充,因为并发模式故障的代价可能很高。如果幸存区的占用量超过初始使用量(占使用年限的百分比),则并发收集也将开始。此初始占用阈值的默认值约为92%,但是该值可能会因版本而异。可以使用命令行选项手动调整此值-XX:CMSInitiatingOccupancyFraction=<N>,其中<N>是占位世代大小的整数百分比(0到100)。

命令行选项及默认值

选项=默认值6以上描述
-XX:+CMSIncrementalMode=启用增量模式。必须同时启用CMS收集器(使用-XX:+UseConcMarkSweepGC),此选项才能起作用。
-XX:+CMSIncrementalPacing=启用自动调整功能。增量模式占空比根据JVM运行时收集的统计信息自动调整。
-XX:CMSIncrementalDutyCycle=10允许CMS收集器运行次要收集之间的时间百分比(0到100)。如果CMSIncrementalPacing启用,则这只是初始值。
-XX:CMSIncrementalDutyCycleMin=0CMSIncrementalPacing启用时,占空系数下限的百分比(0到100)。
-XX:CMSIncrementalSafetyFactor=10计算占空比时用于增加保守性的百分比(0到100)
-XX:CMSIncrementalOffset=0在次要收集之间的时间段内,增量模式占空比向右移动的百分比(0到100)。
-XX:CMSExpAvgFactor=25计算CMS收集统计信息的指数平均值时,用于加权当前样本的百分比(0到100)。

JDK8使用CMS

-XX:+ UseConcMarkSweepGC -XX:+ CMSIncrementalMode \
-XX:+ PrintGCDetails -XX:+ PrintGCTimeStamps</span>
  • 前两个选项分别启用CMS收集器和i-cms。不需要最后两个选项。它们只是使有关垃圾收集的诊断信息写入标准输出,因此可以看到垃圾收集行为并在以后进行分析。

自动调整参数设置

i-cms自动调整功能使用程序运行时收集的统计信息来计算占空比,以便并发收集在堆变满之前完成。但是,过去的行为并不是未来行为的完美预测,并且估计值可能并不总是足够准确以防止堆变满。如果出现了太多的完整集合,可以使用如果参数进行故障排除

命令行选项描述
-XX:CMSIncrementalSafetyFactor=增加安全系数。0-100
-XX:CMSIncrementalDutyCycleMin=增加最小占空比。0-100
-XX:-CMSIncrementalPacing -XX:CMSIncrementalDutyCycle=禁用自动调整,并使用固定的占空比。0-100

 

垃圾优先的垃圾收集器G1(Garbage-First)

用于具有大内存的多处理器计算机。它在实现高吞吐量的同时极有可能满足垃圾收集暂停时间目标,但并不能绝对

 

堆被划分为一组大小相等的堆区域,每个堆区域都有一个连续的虚拟内存范围。G1执行并发全局标记阶段,以确定整个堆中对象的活动性。标记阶段完成后,G1知道哪些区域大部分为空。它首先收集这些区域,这通常会产生大量的自由空间。这就是为什么这种垃圾收集方法称为“垃圾优先”的原因。顾名思义,G1将其收集和压缩活动集中在可能充满可回收对象(即垃圾)的堆区域。G1使用暂停预测模型来满足用户定义的暂停时间目标,并根据指定的暂停时间目标选择要收集的区域数。

G1将对象从堆的一个或多个区域复制到堆上的单个区域,并在此过程中压缩并释放内存。复制过程是在多处理器上并行执行的,以减少暂停时间并增加吞吐量。因此,对于每个垃圾回收,G1都会不断减少碎片。这超出了先前两种方法的能力。CMS(并发标记扫描)垃圾收集不会进行压缩。并行压缩仅执行整个堆压缩,这会导致相当长的暂停时间。

重要的是要注意,G1不是实时收集器。它很有可能达到设定的暂停时间目标,但并非绝对确定。根据先前收集的数据,G1估计在目标时间内可以收集多少个区域。因此,收集器具有收集区域成本的合理准确的模型,并且收集器使用此模型来确定要收集哪些和多少个区域,同时保持在暂停时间目标之内。

G1的首要重点是为运行需要大堆且GC延迟有限的应用程序的用户提供解决方案。这意味着堆大小约为6 GB或更大,并且稳定且可预测的暂停时间低于0.5秒。

从逻辑上来看,G1也是分代模型。一组空区域被指定为逻辑年轻代。在图中,年轻代是浅蓝色的。分配是从逻辑上年轻代中完成,当年轻代已满时,该区域集将被垃圾收集(一个年轻的集合)。在某些情况下,可以同时收集一组年轻区域之外的区域(深蓝色的旧区域)。这称为混合集合。在图中,正在收集的区域用红色框标记。该图说明了混合的集合,因为同时收集了年轻区域和旧区域。垃圾收集是一个压缩收集,它将活动对象复制到选定的最初为空的区域。根据幸存对象的年龄,可以将对象复制到幸存者区域(标有“ S”)或复制到旧区域(未具体显示)。标有“ H”的区域包含超大对象大于一个区域的一半,并且经过特殊处理;

G1 GC是一个区域化分代垃圾收集器,Java堆被划分为2048(目标是不超过)个区域。启动时,Java虚拟机(JVM)设置区域大小。区域大小根据堆大小可以从1 MB到32 MB不等。伊甸园,幸存者和前几代人是这些地区的逻辑集合,并不相邻。

如果应用程序具有以下一个或多个特征,建议使用G1

  • 超过50%的Java堆被实时数据占用。

  • 对象分配率或提升率差异很大。

  • 该应用程序不希望长时间的垃圾收集或压缩暂停(长于0.5到1秒)。

G1标记周期阶段

  • 初始标记阶段:G1 GC在此阶段标记根。此阶段由常规(STW)的年轻垃圾回收承载。

  • 根区域扫描阶段:G1 GC扫描在初始标记阶段标记的幸存者区域,以参考旧一代并标记所参考的对象。该阶段与应用程序(不是STW)同时运行,并且必须在下一个STW年轻垃圾收集开始之前完成。

  • 并发标记阶段:G1 GC在整个堆中找到可访问的(活动的)对象。此阶段与应用程序同时发生,并且可以被STW年轻的垃圾回收中断。

  • 标记阶段:此阶段是STW收集,有助于完成标记周期。G1 GC耗尽SATB缓冲区,跟踪未访问的活动对象,并执行参考处理。

  • 清理阶段:在最后阶段,G1 GC执行记帐和RSet清理的STW操作。在记帐期间,G1 GC会识别出完全空闲的区域和混合垃圾收集候选对象。清除阶段在重置并将空闲区域返回到空闲列表时,部分处于并发状态。

重要默认值

选项和默认值描述
-XX:G1HeapRegionSize=n设置G1区域的大小。该值为2的幂,范围为1 MB至32 MB。目标是基于最小的Java堆大小具有大约2048个区域。
-XX:MaxGCPauseMillis=200为所需的最大暂停时间设置目标值。默认值为200毫秒。指定的值不适合您的堆大小。
-XX:G1NewSizePercent=5设置要用作年轻代大小的最小值的堆百分比。默认值为Java堆的5%。这是一个实验性标志。此设置代替-XX:DefaultMinNewGenPercent设置。
-XX:G1MaxNewSizePercent=60设置堆大小的百分比,以用作年轻代大小的最大值。默认值为Java堆的60%。这是一个实验性标志。此设置代替-XX:DefaultMaxNewGenPercent设置。
-XX:ParallelGCThreads=n设置STW工作线程的值。n为逻辑处理器的数量相同,最多为8。如果逻辑处理器多于八个,则将n的值设置为逻辑处理器的大约5/8。除较大的SPARC系统外,这在大多数情况下都有效,其中n的值约为逻辑处理器的5/16。
-XX:ConcGCThreads=n设置平行标记线的数量。设置n为并行垃圾回收线程数(ParallelGCThreads)的大约1/4。
-XX:InitiatingHeapOccupancyPercent=45设置触发标记周期的Java堆占用阈值。默认占用率是整个Java堆的45%。
-XX:G1MixedGCLiveThresholdPercent=85设置要包含在混合垃圾收集周期中的旧区域的占用阈值。默认占用率为85%。这是一个实验性标志。此设置代替-XX:G1OldCSetRegionLiveThresholdPercent设置。
-XX:G1HeapWastePercent=5设置您愿意浪费的堆百分比。当可回收百分比小于堆垃圾百分比时,Java HotSpot VM不会启动混合垃圾回收周期。默认值为5%
-XX:G1MixedGCCountTarget=8设置标记周期后混合垃圾回收的目标数量,以收集最多包含G1MixedGCLIveThresholdPercent实时数据的旧区域。默认值为8个混合垃圾回收。混合GC的目标是在此目标数量之内
-XX:G1OldCSetRegionThresholdPercent=10设置在混合垃圾收集周期中要收集的旧区域数的上限。缺省值为Java堆的10%
-XX:G1ReservePercent=10设置保留内存的百分比以使其保持空闲状态,以减少空间溢出的风险。默认值为10%。当增加或减少百分比时,请确保将总Java堆调整为相同的数量

 

在评估和微调GC GC时,请牢记以下建议

  • 年轻代的大小:避免使用该-Xmn选项或任何其他相关选项(例如)明确设置年轻代的大小-XX:NewRatio。固定年轻代的大小会覆盖目标暂停时间目标。

  • 暂停时间目标:当您评估或调整任何GC时,总会有延迟与吞吐量之间的权衡。G1 GC是具有统一暂停的增量垃圾收集器,但在应用程序线程上也有更多开销。G1 GC的吞吐量目标是90%的应用时间和10%的垃圾收集时间。将此与Java HotSpot VM并行收集器进行比较。并行收集器的吞吐量目标是99%的应用程序时间和1%的垃圾收集时间。因此,在评估G1 GC的吞吐量时,请放宽暂停时间目标。设置过于激进的目标表示您愿意承担垃圾收集开销的增加,这直接影响了吞吐量。在评估G1 GC的延迟时,您可以设置所需的(软)实时目标,G1 GC会尝试达到此目标。副作用是,吞吐量可能会受到影响。

  • 改良混合垃圾收集:调整混合垃圾收集时,请尝试以下选项。有关这些选项的信息,请参阅“ 重要默认值 ”部分:

    • -XX:InitiatingHeapOccupancyPercent:用于更改标记阈值。

    • -XX:G1MixedGCLiveThresholdPercent-XX:G1HeapWastePercent:用于更改混合垃圾收集决策。

    • -XX:G1MixedGCCountTarget-XX:G1OldCSetRegionThresholdPercent:用于调整旧区域的CSet。

 

溢出日志

当您在日志中看到空间溢出或空间耗尽消息时,G1 GC没有足够的内存来存储幸存者或升级对象,或两者都没有。Java堆不能,因为它已经处于最大状态。消息示例:

  • 924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space exhausted), 0.1957310 secs]

  • 924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space overflow), 0.1957310 secs]

要缓解此问题,请尝试以下调整:

  • 增加-XX:G1ReservePercent选项的值(并相应增加总堆),以增加“至空间”的保留内存量。

  • 通过降低的值来更早地开始标记周期 -XX:InitiatingHeapOccupancyPercent.

  • 增加-XX:ConcGCThreads选项的值以增加并行标记线程的数量。

 

巨型对象的分配和回收

  • 对于G1 GC,任何大于区域大小一半的对象都被视为巨大对象。这样的实例在老年代中直接分配到庞大的区域。这些大区域是一组连续的小区域。由参数StartsHumongous标记连续集合的开始并ContinuesHumongous标记集合的连续空间。
  • 在分配任何大型区域之前,将检查标记阈值,并在必要时启动并发循环。
  • 在清理阶段以及整个垃圾收集周期的标记周期结束时,将释放死掉的巨型对象。
  • 为了减少复制开销,大型对象不包括在任何复制搬迁的过程中。完整的垃圾收集周期将庞大的对象压缩到位。
  • 因为StartsHumongous和ContinuesHumongous区域的每个单独集合仅包含一个humongous对象,所以未使用humongous对象的末端与该对象所覆盖的最后一个区域的末端之间的空间。对于略大于堆区域大小倍数的对象,此未使用的空间可能导致堆碎片化。
  • - 如果您看到由于巨大分配而启动并发标记周期,并且这种分配使您的上一代分裂了,那么请增加-XX:G1HeapRegionSize使先前的巨大对象不再是巨大的,并会遵循常规分配路径。

 

弱引用、软引用、虚拟引用与GC,类元数据

  • 弱引用

    当GC时发现一个实例的引用为弱引用时,会直接无视该引用并执行清理动作

  • 软引用

    当内存空间足够时,GC不会回收该区域的内存。直到空间不够时才会回收

    在服务器虚拟机中,软引用的生存期比在客户端中更长。清除速率可以使用命令行选项控制,该选项-XX:SoftRefLRUPolicyMSPerMB=<N>指定对于堆中每兆字节的可用空间,软引用将保持活动状态的毫秒数(一旦不再严格可达)。缺省值为每兆字节1000毫秒,这意味着对于堆中的每兆字节可用空间,软引用(在收集到对对象的最后一个强引用之后)将保留1秒钟。这是一个大概的数字,因为仅在垃圾回收期间才清除软引用,这可能会偶尔发生。

  • 虚拟引用

    用于管理堆外内存空间

  • 显示GC

    应用程序可以与垃圾回收进行交互的另一种方式是通过调用显式调用完整的垃圾回收System.gc()。这可能会强制在没有必要的情况下(例如,当次要收集就足够时)进行主要收集,因此通常应避免使用。可以通过使用标志禁用显式垃圾回收来衡量显式垃圾回收的性能效果-XX:+DisableExplicitGC,这会使VM忽略对的调用System.gc()

    显式垃圾回收最常遇到的用途之一是远程方法调用(RMI)的分布式垃圾回收(DGC)。使用RMI的应用程序引用其他虚拟机中的对象。在不偶尔调用本地堆的垃圾收集的情况下,无法在这些分布式应用程序中收集垃圾,因此RMI会定期强制执行完整收集。可以使用属性控制这些收集的频率,如以下示例所示:

    java -Dsun.rmi.dgc.client.gcInterval = 3600000
        -Dsun.rmi.dgc.server.gcInterval = 3600000 ...
        -Dsun.rmi.dgc.server.gcInterval = 3600000 ...

    本示例指定每小时一次的显式垃圾回收,而不是默认的每分钟一次的回收率。但是,这也可能导致某些对象需要更长的时间才能被回收。Long.MAX_VALUE如果不希望DGC活动的及时性有上限,可以将这些属性设置为高,以使显式集合之间的时间有效无限。

  • 类元数据

    Java类在Java Hotspot VM中具有内部表示形式,称为类元数据。在Java Hotspot VM的早期版本中,类元数据是在所谓的永久生成中分配的。在JDK 8中,永久生成已删除,并且类元数据已分配在本机内存中。默认情况下,可用于类元数据的本地内存量是无限的。使用该选项MaxMetaspaceSize可以将用于类元数据的本机内存量设置为上限。

    Java Hotspot VM显式管理用于元数据的空间。向操作系统请求空间,然后将其分成多个块。类加载器从其块分配元数据的空间(块绑定到特定的类加载器)。当为类加载器卸载类时,其块将被回收以重新使用或返回给OS。元数据使用分配的空间mmap,而不是malloc

    如果UseCompressedOops打开并UseCompressedClassesPointers使用,则将本机内存的两个逻辑上不同的区域用于类元数据。与Java对象引用UseCompressedClassPointers一样UseCompressedOops,使用32位偏移量表示64位进程中的类指针。为这些压缩的类指针分配了一个区域(32位偏移量)。可以设置区域的大小,CompressedClassSpaceSize默认情况下为1 GB。压缩类指针的空间保留为mmap初始化时分配的空间,并根据需要提交。将MaxMetaspaceSize适用于犯下压缩类空间之和为其他类的元数据的空间。

    卸载相应的Java类时,将重新分配类元数据。Java类是作为垃圾回收的结果而卸载的,并且可以引发垃圾回收以便卸载类和取消分配类元数据。当用于类元数据的空间达到一定级别(高水位线)时,将引发垃圾回收。垃圾回收之后,高水位线可能会升高或降低,具体取决于类元数据释放的空间量。高水位线将被抬起,以免过早引起另一次垃圾收集。高水位标记最初设置为命令行选项的值MetaspaceSize。根据选项MaxMetaspaceFreeRatio和选项来升高或降低MinMetaspaceFreeRatio。如果可用于类元数据的承诺空间占用于类元数据的总承诺空间的百分比大于MaxMetaspaceFreeRatio,则高水位线将降低。如果小于MinMetaspaceFreeRatio,则高水位线将升高。

    为该选项指定一个较高的值,MetaspaceSize以避免早期为类元数据引发垃圾回收。为应用程序分配的类元数据的数量取决于应用程序,并且不存在用于选择的通用准则MetaspaceSize。默认大小MetaspaceSize取决于平台,范围从12 MB到大约20 MB。

 

以下GC优化来源于马士兵老师的公开课

常见的垃圾回收器

  1. 垃圾回收器的发展路线,是随着内存越来越大的过程而演进 从分代算法演化到不分代算法 Serial算法 几十兆 Parallel算法 几个G CMS 几十个G - 承上启下,开始并发回收 - .- 三色标记 -

  2. JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS 并发垃圾回收是因为无法忍受STW

  3. Serial 年轻代 串行回收

  4. PS 年轻代 并行回收

  5. ParNew 年轻代 配合CMS的并行回收

  6. SerialOld

  7. ParallelOld

  8. ConcurrentMarkSweep 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms) CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定 CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收 想象一下: PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW) 几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC 算法:三色标记 + Incremental Update

  9. G1(200ms - 10ms) 算法:三色标记 + SATB

  10. ZGC (10ms - 1ms) PK C++ 算法:ColoredPointers + LoadBarrier

  11. Shenandoah 算法:ColoredPointers + WriteBarrier

  12. Eplison

  13. PS 和 PN区别的延伸阅读: ▪https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73

  14. 垃圾收集器跟内存大小的关系

    1. Serial 几十兆

    2. PS 上百兆 - 几个G

    3. CMS - 20G

    4. G1 - 上百G

    5. ZGC - 4T - 16T(JDK13)

1.8默认的垃圾回收:PS + ParallelOld

常见垃圾回收器组合参数设定:(1.8)

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old

    • 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器

  • -XX:+UseParNewGC = ParNew + SerialOld

    • 这个组合已经很少用(在某些版本中已经废弃)

  • -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old

  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】

  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old

  • -XX:+UseG1GC = G1

  • Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC

    • java +XX:+PrintCommandLineFlags -version

    • 通过GC的日志来分辨

  • Linux下1.8版本默认的垃圾回收器到底是什么?

    • 1.8.0_181 默认(看不出来)Copy MarkCompact

    • 1.8.0_222 默认 PS + PO

JVM调优第一步,了解JVM常用命令行参数

  • HotSpot参数分类

    标准: - 开头,所有的HotSpot都支持

    非标准:-X 开头,特定版本HotSpot支持特定命令

    不稳定:-XX 开头,下个版本可能取消

    java -version

    java -X

     

    java -XX:+PrintFlagsWithComments //只有debug版本能用

     

    试验用程序:

    import java.util.List;
    import java.util.LinkedList;
    
    public class HelloGC {
      public static void main(String[] args) {
        System.out.println("HelloGC!");
        List list = new LinkedList();
        for(;;) {
          byte[] b = new byte[1024*1024];
          list.add(b);
      }
      }
    }

     

    1. 区分概念:内存泄漏memory leak,内存溢出out of memory

    2. java -XX:+PrintCommandLineFlags HelloGC

    3. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC PrintGCDetails PrintGCTimeStamps PrintGCCauses

    4. java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC

    5. java -XX:+PrintFlagsInitial 默认参数值

    6. java -XX:+PrintFlagsFinal 最终参数值

    7. java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数

    8. java -XX:+PrintFlagsFinal -version |grep GC

    9. java -XX:+PrintFlagsFinal -version | wc -l 共728个参数

PS GC日志详解

每种垃圾回收器的日志格式是不同的!

PS日志格式

 

heap dump部分:

<span style="color:#333333">eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
                            后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址</span>

 

total = eden + 1个survivor

调优前的基础概念:

  1. 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)

  2. 响应时间:STW越短,响应时间越好

所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量...

问题:

科学计算,吞吐量。数据挖掘,thrput。吞吐量优先的一般:(PS + PO)

响应时间:网站 GUI API (1.8 G1)

什么是调优?

  1. 根据需求进行JVM规划和预调优

  2. 优化运行JVM运行环境(慢,卡顿)

  3. 解决JVM运行过程中出现的各种问题(OOM)

调优,从规划开始

  • 调优,从业务场景开始,没有业务场景的调优都是耍流氓

  • 无监控(压力测试,能看到结果),不调优

  • 步骤:

    1. 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)

      1. 响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)

      2. 吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]

    2. 选择回收器组合

    3. 计算内存需求(经验值 1.5G 16G)

    4. 选定CPU(越高越好)

    5. 设定年代大小、升级年龄

    6. 设定日志参数

      1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause

      2. 或者每天产生一个日志文件

    7. 观察日志情况

  • 案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?

    这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G 16G)

    1小时360000集中时间段, 100个订单/秒,(找一小时内的高峰期,1000订单/秒)

    经验值,

    非要计算:一个订单产生需要多少内存?512K * 1000 500M内存

    专业一点儿问法:要求响应时间100ms

    压测!

  • 案例2:12306遭遇春节大规模抢票应该如何支撑?

    12306应该是中国并发量最大的秒杀网站:

    号称并发量100W最高

    CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器

    普通电商订单 -> 下单 ->订单系统(IO)减库存 ->等待用户付款

    12306的一种可能的模型: 下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款

    减库存最后还会把压力压到一台服务器

    可以做分布式本地库存 + 单独服务器做库存均衡

    大流量的处理方法:分而治之

  • 怎么得到一个事务会消耗多少内存?

    1. 弄台机器,看能承受多少TPS?是不是达到目标?扩容或调优,让它达到

    2. 用压测来确定

优化环境

  1. 有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G 的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G 的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了

    1. 为什么原网站慢? 很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢

    2. 为什么会更卡顿? 内存越大,FGC时间越长

    3. 咋办? PS -> PN + CMS 或者 G1

  2. 系统CPU经常100%,如何调优?(面试高频) CPU100%那么一定有线程在占用系统资源,

    1. 找出哪个进程cpu高(top)

    2. 该进程中的哪个线程cpu高(top -Hp)

    3. 导出该线程的堆栈 (jstack)

    4. 查找哪个方法(栈帧)消耗时间 (jstack)

    5. 工作线程占比高 | 垃圾回收线程占比高

  3. 系统内存飙高,如何查找问题?(面试高频)

    1. 导出堆内存 (jmap)

    2. 分析 (jhat jvisualvm mat jprofiler ... )

  4. 如何监控JVM

    1. jstat jvisualvm jprofiler arthas top...

解决JVM运行中的问题

一个案例理解常用工具

  1. 测试代码:

    package com.mashibing.jvm.gc;
    
    import java.math.BigDecimal;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.ScheduledThreadPoolExecutor;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
     */
    
    public class T15_FullGC_Problem01 {
    
        private static class CardInfo {
            BigDecimal price = new BigDecimal(0.0);
            String name = "张三";
            int age = 5;
            Date birthdate = new Date();
    
            public void m() {}
        }
    
        private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
                new ThreadPoolExecutor.DiscardOldestPolicy());
    
        public static void main(String[] args) throws Exception {
            executor.setMaximumPoolSize(50);
    
            for (;;){
                modelFit();
                Thread.sleep(100);
            }
        }
    
        private static void modelFit(){
            List<CardInfo> taskList = getAllCardInfo();
            taskList.forEach(info -> {
                // do something
                executor.scheduleWithFixedDelay(() -> {
                    //do sth with info
                    info.m();
    
                }, 2, 3, TimeUnit.SECONDS);
            });
        }
    
        private static List<CardInfo> getAllCardInfo(){
            List<CardInfo> taskList = new ArrayList<>();
    
            for (int i = 0; i < 100; i++) {
                CardInfo ci = new CardInfo();
                taskList.add(ci);
            }
    
            return taskList;
        }
    }
    
  2. java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01

  3. 一般是运维团队首先受到报警信息(CPU Memory)

  4. top命令观察到问题:内存不断增长 CPU占用率居高不下

  5. top -Hp 观察进程中的线程,哪个线程CPU和内存占比高

  6. jps定位具体java进程 jstack 定位线程状况,重点关注:WAITING BLOCKED eg. waiting on <0x0000000088ca3310> (a java.lang.Object) 假如有一个进程中100个线程,很多线程都在waiting on <xx> ,一定要找到是哪个线程持有这把锁 怎么找?搜索jstack dump的信息,找<xx> ,看哪个线程持有这把锁RUNNABLE 作业:1:写一个死锁程序,用jstack观察 2 :写一个程序,一个线程持有锁不释放,其他线程等待

  7. 为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称 怎么样自定义线程池里的线程名称?(自定义ThreadFactory)

  8. jinfo pid

  9. jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用) jstat -gc 4655 500 : 每个500个毫秒打印GC的情况 如果面试官问你是怎么定位OOM问题的?如果你回答用图形界面(错误) 1:已经上线的系统不用图形界面用什么?(cmdline arthas) 2:图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察)

  10. jmap - histo 4655 | head -20,查找有多少对象产生

  11. jmap -dump:format=b,file=xxx pid :

    线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合) 1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件(不是很专业,因为多有监控,内存增长就会报警) 2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响 3:在线定位(一般小点儿公司用不到)

    4:在测试环境中压测(产生类似内存增长问题,在堆还不是很大的时候进行转储)

  12. java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01

  13. 使用MAT / jhat /jvisualvm 进行dump文件分析 https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html jhat -J-mx512M xxx.dump http://192.168.17.11:7000 拉到最后:找到对应链接 可以使用OQL查找特定问题对象

  14. 找到代码的问题

jconsole远程连接

  1. 程序启动加入参数:

    java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
  2. 如果遭遇 Local host name unknown:XXX的错误,修改/etc/hosts文件,把XXX加入进去

    192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4
    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
  3. 关闭linux防火墙(实战中应该打开对应端口)

    service iptables stop
    chkconfig iptables off #永久关闭
  4. windows上打开 jconsole远程连接 192.168.17.11:11111

jvisualvm远程连接

https://www.cnblogs.com/liugh/p/7620336.html (简单做法)

jprofiler (收费)

arthas在线排查工具

  • 为什么需要在线排查? 在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。

  • jvm观察jvm信息

  • thread定位线程问题

  • dashboard 观察系统情况

  • heapdump + jhat分析

  • jad反编译 动态代理生成类的问题定位 第三方的类(观察代码) 版本问题(确定自己最新提交的版本是不是被使用)

  • redefine 热替换 目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性 m() -> mm()

  • sc - search class

  • watch - watch method

  • 没有包含的功能:jmap

GC算法的基础概念

  • Card Table 由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card 在结构上,Card Table用BitMap来实现

CMS

CMS的问题

  1. Memory Fragmentation

    -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩

  2. Floating Garbage

    Concurrent Mode Failure 产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped

    解决方案:降低触发CMS的阈值

    PromotionFailed

    解决方案类似,保持老年代有足够的空间

    –XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间

CMS日志分析

执行命令:java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01

[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

ParNew:年轻代收集器

6144->640:收集前后的对比

(6144):整个年轻代容量

6585 -> 2770:整个堆的情况

(19840):整个堆大小

 

<span style="color:#333333">[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
	//8511 (13696) : 老年代使用(最大)
	//9866 (19840) : 整个堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
	//这里的时间意义不大,因为是并发执行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
	//标记Card为Dirty,也称为Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
	//STW阶段,YG occupancy:年轻代占用及容量
	//[Rescan (parallel):STW下的存活对象标记
	//weak refs processing: 弱引用处理
	//class unloading: 卸载用不到的class
	//scrub symbol(string) table: 
		//cleaning up symbol and string tables which hold class-level metadata and 
		//internalized string respectively
	//CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量
	//10108K(19840K): 阶段过后的堆占用及容量

[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
	//标记已经完成,进行并发清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
	//重置内部结构,为下次GC做准备</span>

 

G1

G1日志详解

<span style="color:#333333">[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年轻代 Evacuation-> 复制存活对象 
//initial-mark 混合回收的阶段,这里是YGC混合老年代回收
   [Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程
      [GC Worker Start (ms):  92635.7]
      [Ext Root Scanning (ms):  1.1]
      [Update RS (ms):  0.0]
         [Processed Buffers:  1]
      [Scan RS (ms):  0.0]
      [Code Root Scanning (ms):  0.0]
      [Object Copy (ms):  0.1]
      [Termination (ms):  0.0]
         [Termination Attempts:  1]
      [GC Worker Other (ms):  0.0]
      [GC Worker Total (ms):  1.2]
      [GC Worker End (ms):  92636.9]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.1 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.0 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
//以下是混合回收其他阶段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//无法evacuation,进行FGC
[Full GC (Allocation Failure)  18M->18M(20M), 0.0719656 secs]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
</span>

 

GC常用参数

  • -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间

  • -XX:+UseTLAB 使用TLAB,默认打开

  • -XX:+PrintTLAB 打印TLAB的使用情况

  • -XX:TLABSize 设置TLAB大小

  • -XX:+DisableExplictGC System.gc()不管用 ,FGC

  • -XX:+PrintGC

  • -XX:+PrintGCDetails

  • -XX:+PrintHeapAtGC

  • -XX:+PrintGCTimeStamps

  • -XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间

  • -XX:+PrintGCApplicationStoppedTime (低) 打印暂停时长

  • -XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用

  • -verbose:class 类加载详细过程

  • -XX:+PrintVMOptions

  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用

  • -Xloggc:opt/log/gc.log

  • -XX:MaxTenuringThreshold 升代年龄,最大值15

  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 ... 这些不建议设置

Parallel常用参数

  • -XX:SurvivorRatio

  • -XX:PreTenureSizeThreshold 大对象到底多大

  • -XX:MaxTenuringThreshold

  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同

  • -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

CMS常用参数

  • -XX:+UseConcMarkSweepGC

  • -XX:ParallelCMSThreads CMS线程数量

  • -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)

  • -XX:+UseCMSCompactAtFullCollection 在FGC时进行压缩

  • -XX:CMSFullGCsBeforeCompaction 多少次FGC之后进行压缩

  • -XX:+CMSClassUnloadingEnabled

  • -XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm回收

  • GCTimeRatio 设置GC时间占用程序运行时间的百分比

  • -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

G1常用参数

  • -XX:+UseG1GC

  • -XX:MaxGCPauseMillis 建议值,G1会尝试调整Young区的块数来达到这个值

  • -XX:GCPauseIntervalMillis ?GC的间隔时间

  • -XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。 随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 ZGC做了改进(动态区块大小)

  • G1NewSizePercent 新生代最小比例,默认为5%

  • G1MaxNewSizePercent 新生代最大比例,默认为60%

  • GCTimeRatio GC时间建议比例,G1会根据这个值调整堆空间

  • ConcGCThreads 线程数量

  • InitiatingHeapOccupancyPercent 启动G1的堆空间占用比例

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值