JVM虚拟机(十二)ParallelGC、CMS、G1垃圾收集器的 GC 日志解析

news2024/11/17 21:34:15

目录

    • 一、如何开启 GC 日志?
    • 二、GC 日志分析
      • 2.1 PS+PO 日志分析
      • 2.2 ParNew+CMS 日志分析
      • 2.3 G1 日志分析
    • 三、GC 发生的原因
      • 3.1 Allocation Failure:新生代空间不足,触发 Minor GC
      • 3.2 Metadata GC Threshold:元数据(方法区)空间不足,触发 Full GC
      • 3.3 Ergonomics:系统调用,触发 Full GC
      • 3.4 System.gc():手动调用,触发 Full GC
    • 四、实战:项目启动时速度很慢

  • 在进行 JVM 性能调优的过程中,经常要借助于 GC 日志。
  • 其实不同的垃圾收集器产生的 GC 日志大致遵循了同一个规则,只是有些许不同,不过对于 G1 收集器的 GC 日志和其他垃圾收集器有较大差别。

一、如何开启 GC 日志?

发生 GC 之后,我们要分析 GC 日志,当然就首先要拿到 GC 日志,打印 GC 日志可以通过如下命令:

java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=14 -XX:GCLogFileSize=20M -XX:+PrintHeapAtGC -XX:+HeapDumpOnOutOfMemoryError -Xloggc:springboot-demo-gc".log" -jar springboot-demo.jar

  • -XX:PrintGCDetails:打印更详细的 GC 日志信息,包括各个内存区域的使用情况、GC 的触发原因等。
  • -XX:+PrintGCTimesStamps:打印 GC 发生的时间戳。(以基准时间的形式)
  • -XX:+PrintGCDateStamps:打印 GC 发生的时间戳。(以标准时间的形式,如:2024-04-13T17:35:04.859+0800)。
  • -XX:+UseGCLogFileRotation:启用 GC 日志文件的自动轮转功能,维持日志目录中的日志文件在一定的数目。
  • -XX:NumberOfGCLogFiles=14:GC 日志文件的滚动数量,使 GC 日志文件数量维持在 14 个,超出 14 个后,将日志覆盖写入到最早的日志文件中。
  • -XX:GCLogFileSize=20M:GC 日志文件的大小,超出大小后触发日志的轮转,将日志覆盖写入到最早的日志文件中。
  • -XX:+PrintHeapAtGC:在进行 GC 的前后打印出堆的信息。
  • -XX:+HeapDumpOnOutOfMemoryError:内存溢出的时候生成 dump 文件。
  • -XX:HeapDumpPath=目录/xxx.hprof:指定内存溢出时,dump 文件的生成路径,默认为当前目录。
  • -Xloggc:springboot-demo-gc".log":指定 GC 日志的输出文件。

二、GC 日志分析

2.1 PS+PO 日志分析

按照上面的步骤开启 GC 日志后,如果一次 GC 都没发生的话日志文件是空的。可以等到发生 GC 之后再打开,文件内容如下所示:

在这里插入图片描述

前面3行应该都能看懂:

  1. 第一行,打印的是当前所使用的 HotSpot 虚拟机及其对应版本号;
  2. 第二行,打印的是操作系统相关的内存信息;
  3. 第三行,打印的是当前 Java 服务启动后所配置的参数信息。这里可以看到目前使用的是默认的 Parallel GC。

下面第4行开始才是我们的 GC 日志,首先我们举一个 Young GC 日志的解析:

在这里插入图片描述

2020-08-23T15:35:30.747+0800: 5.486: [GC (Allocation Failure) [PSYoungGen: 32768K->3799K(37888K)] 32768K->3807K(123904K), 0.1129986 secs] [Times: user=0.02 sys=0.00, real=0.11 secs] 
  • 2020-08-23T15:35:30.747+0800:代表的是垃圾回收发生的时间。

  • 5.486:表示的是从 Java 虚拟机启动以来经过的秒数。

  • GC (Allocation Failure):表示发生 GC 的原因,这里是由于分配空间失败而发生了 GC。

  • [PSYoungGen: 32768K->3799K(37888K)]

    • PSYoungGen: PS 表示的是 Parallel Scavenge 收集器,YoungGen 表示当前发生的是年轻代的垃圾回收。这里不同的垃圾收集器会有不同的名字,如:ParNew 收集器就会显示为 ParNew。
    • 32768K->3799K(37888K): 表示 GC 发生之前使用的内存空间大小为32768K,GC 发生之后使用的内存空间大小为3799K,年轻代的总容量为37888K。
  • 32768K->3807K(123904K):表示 GC 发生之前 Java堆已使用容量为32768K,GC 发生之后 Java堆已使用容量为3807K,Java堆的总容量为123904K。

    注意: YoungGen 和 Java堆中这两组 GC 前后的数字相减得到的值一般是不相等的,这是因为总空间下面还包括了老年代发生回收后释放的空间大小。可能有人会觉得奇怪,这里明明只有新生代发生了 GC,为什么老年代会有空间释放?这是因为 S 区如果空间不够的话会利用 担保机制 向老年代借用空间,所以借来的空间是可能被释放的。

  • 0.1129986 secs:表示的是 GC 所花费的时间,secs 表示单位是秒。

  • [Times: user=0.02 sys=0.00, real=0.11 secs]:这一部分并不是所有的垃圾收集器都会打印。

    • user=0.02: 表示用户态消耗的 CPU 时间。
    • sys=0.00: 表示内耗态消耗的 CPU 时间和操作从开始到结束所经过的墙钟时间,即:sys=CPU时间+墙钟时间。

补充: 墙钟时间(Wall Clock Time) 包括各种非运算的等待耗时,例如:等待磁盘I/O、等待线程阻塞,而 CPU 时间不包括这些不需要消耗 CPU 的时间。

下面我们再看一下 Full GC 的日志解析:

在这里插入图片描述

2020-08-23T15:35:34.635+0800: 9.374: [Full GC (Metadata GC Threshold) [PSYoungGen: 5092K->0K(136192K)] [ParOldGen: 12221K->12686K(63488K)] 17314K->12686K(199680K), [Metaspace: 20660K->20660K(1067008K)], 0.0890985 secs] [Times: user=0.25 sys=0.00, real=0.09 secs] 
  • 基本信息同新生代一样,就不再赘述了。
  • Full GC:表示发生了 Full GC,Full GC=Minor GC+Major GC+Metaspace GC。所以后面可以看到 3 个区域的回收信息:PSYoungGen、ParOldGen、Metaspace。而且这 3 个区域对比非常明显,新生代全部回收掉了,老年代回收了一小部分,而方法区一点都没有回收掉,这也体现了 3 个区域中存储内容的区别。
  • ParOldGen:表示 Parallel Old 收集器在回收老年代。
  • Metaspace:表示的是方法区/元空间(JDK7是永久代)。

2.2 ParNew+CMS 日志分析

我们将垃圾收集器切换为 CMS,命令如下:

-XX:+UseConcMarkSweepGC

注意:CMS 是一款老年代收集器,使用这个参数后新生代默认会使用 ParNew 收集器。

将 CMS 垃圾收集器配置好之后重启服务,等待垃圾回收之后,打开 GC 日志如下:

在这里插入图片描述

前面3行应该都能看懂:

  1. 第一行,打印的是当前所使用的 HotSpot 虚拟机及其对应版本号;
  2. 第二行,打印的是操作系统相关的内存信息;
  3. 第三行,打印的是当前 Java 服务启动后所配置的参数信息。可以看到垃圾收集器已经成功切到 ParNew 和 CMS。

首先我们来看一下 ParNew 的 Young GC 日志:

2024-04-20T23:25:16.823+0800: 2.236: [GC (Allocation Failure) 2024-04-20T23:25:16.825+0800: 2.237: [ParNew: 67200K->7459K(75584K), 0.0085634 secs] 67200K->7459K(243520K), 0.0108369 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
  • 这里的回收信息和上面 PS 的 GC 日志基本一样,只是新生代名称不一样,这里叫 ParNew。

下面我们看一下老年代垃圾收集器 CMS:

CMS 的相关知识

CMS 全程 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器。该回收器是针对老年代垃圾回收的,是一款以获取最短回收停顿时间为目标的收集器,停顿时间短,用户体验就好。其最大特点是在进行垃圾回收时,应用仍然能正常运行。

在这里插入图片描述

CMS 整个过程分为 4 步:

  1. 初始标记(initial mark)

    需要 Stop The World。标记 GC Root 对象,因为 GC Root 对象并不会很多,所以这个过程非常快。

  2. 并发标记(concurrent mark)

    这个阶段可以和用户线程同时进行,也可以分为3步:

    (1)并发标记(CMS-concurrent-mask)

    主要是进行 GC Roots Tracing。就是说根据第1步中找到的 GC Root 对象,开始搜索,这个过程相比阶段1是比较慢的。

    (2)预清理(CMS-concurrent-preclean)

    这个阶段是为了并发标记之后发生了变化的对象。

    (3)可被终止的预清理(CMS-concurrent-abortable-preclean)

    跟预清理差不多,但是是可以被种植的,主要是为了尽可能分担下面第3步重新标记的工作,这个阶段会有一个 abort 触发条件。该阶段存在的目的是希望能发生一次 Young GC,这样就可以减少 Young 区对象的数量,降低重新标记的工作量,因为重新标记会扫描整个堆内空间。可以通过参数 -XX:CMSScavengeBeforeRemark 参数控制在重新标记前发生一次 Young GC,默认为 false。这个阶段发生的最大时间由 -XX:CMSMaxAbortablePrecleanTime 控制,默认 5s。

  3. 重新标记(remark)

    需要 Stop The World,这个阶段是为了修正在阶段2并发标记之后产生了变化的对象。

  4. 并发清除(concurrent sweep)

    和用户线程同时进行,开始正式清除垃圾,在此阶段也会产生垃圾,产生垃圾后无法清除,只能等待下一次 GC。

我们主要看看老年代垃圾收集器 CMS 的 GC 日志,我们把一个完整的老年代回收日志复制出来:

// CMS Initial Mark:对应的是 CMS 工作机制的第1步——初始标记,主要是寻找 GC Root 对象。
// 30298K(86016K):表示当前 CMS 区域已使用空间30298K,总容量86016K。
// 34587K(124736K):表示当前 Java堆已使用空间34587K,总容量124736K。
// 0.0014342 secs:表示 CMS 初始标记阶段耗时约为0.0014342秒。
// [Times: user=0.00 sys=0.00, real=0.00 secs]:表示操作系统统计这段时间内,用户态CPU耗时、内核态CPU耗时、实际耗时均为0。
2020-08-23T17:00:47.650+0800: 18.182: [GC (CMS Initial Mark) [1 CMS-initial-mark: 30298K(86016K)] 34587K(124736K), 0.0014342 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
// CMS-concurrent-mark-start:对应的是 CMS 工作机制中的第2步的第1小步——并发标记。这个阶段主要是根据 GC Root 对象表里整个引用链。
2020-08-23T17:00:47.651+0800: 18.183: [CMS-concurrent-mark-start]
// 0.061/0.061 secs:表示该阶段持续的 CPU 时间和墙钟时间。
2020-08-23T17:00:47.712+0800: 18.244: [CMS-concurrent-mark: 0.061/0.061 secs] [Times: user=0.13 sys=0.00, real=0.06 secs] 
// CMS-concurrent-preclean-start:对应的是 CMS 工作机制中的第2步的第2小步——预清理。
2020-08-23T17:00:47.712+0800: 18.244: [CMS-concurrent-preclean-start]
2020-08-23T17:00:47.714+0800: 18.245: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
// CMS-concurrent-abortable-preclean-start:对应的是 CMS 工作机制中的第2步的第3小步——可被终止的预清理。
2020-08-23T17:00:47.714+0800: 18.246: [CMS-concurrent-abortable-preclean-start]
// Young GC:在重新标记之前进行一次年轻代的垃圾回收,减少重新标记时的STW时间。
2020-08-23T17:00:48.143+0800: 18.674: [GC (Allocation Failure) 2020-08-23T17:00:48.143+0800: 18.674: [ParNew: 38720K->4111K(38720K), 0.0101415 secs] 69018K->38573K(124736K), 0.0102502 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
2020-08-23T17:00:48.451+0800: 18.983: [CMS-concurrent-abortable-preclean: 0.274/0.737 secs] [Times: user=0.94 sys=0.13, real=0.74 secs] 
// CMS Final Remark:对应的是 CMS 工作机制中的第3步——重新标记,此阶段需要STW。可以看到,在此阶段前发生了一次 Young GC,这是为了减少STW时间。
2020-08-23T17:00:48.451+0800: 18.983: [GC (CMS Final Remark) [YG occupancy: 23345 K (38720 K)]2020-08-23T17:00:48.451+0800: 18.983: [Rescan (parallel) , 0.0046112 secs]2020-08-23T17:00:48.456+0800: 18.987: [weak refs processing, 0.0006259 secs]2020-08-23T17:00:48.457+0800: 18.988: [class unloading, 0.0062187 secs]2020-08-23T17:00:48.463+0800: 18.994: [scrub symbol table, 0.0092387 secs]2020-08-23T17:00:48.472+0800: 19.004: [scrub string table, 0.0006408 secs][1 CMS-remark: 34461K(86016K)] 57806K(124736K), 0.0219024 secs] [Times: user=0.05 sys=0.01, real=0.02 secs] 
// CMS-concurrent-sweep:对应的是 CMS 工作集中的第4步——并发清除,并发清除垃圾。
2020-08-23T17:00:48.473+0800: 19.005: [CMS-concurrent-sweep-start]
2020-08-23T17:00:48.489+0800: 19.020: [CMS-concurrent-sweep: 0.015/0.015 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
// CMS-concurrent-reset-start:重置线程。
2020-08-23T17:00:48.489+0800: 19.020: [CMS-concurrent-reset-start]
2020-08-23T17:00:48.492+0800: 19.023: [CMS-concurrent-reset: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

2.3 G1 日志分析

首先,通过配置切换到 G1 垃圾收集器:

-XX:+UseG1GC

修改配置后需要重新启动服务,等待一次 GC 之后,打开 GC 日志文件内容如下:

在这里插入图片描述

通过前 3 行日志可以看到,目前程序已经成功切换到 G1 垃圾收集器了。我们找一次完成的 G1 日志进行分析:

// (young), 0.0029103 secs:表示发生 GC 的区域是 Young 区,总耗时时间为 0.0029103秒。
2020-08-23T18:44:39.787+0800: 2.808: [GC pause (G1 Evacuation Pause) (young), 0.0029103 secs]
   // [Parallel Time: 1.9 ms, GC Workers: 4]:表示线程的并行时间是 1.9毫秒,垃圾回收并行的线程数是4。
   [Parallel Time: 1.9 ms, GC Workers: 4]
	  /** 接下来的几行描述了每个 GC 工作线程在各个阶段的工作情况。 */
	  // GC Worker Start (ms):表示各工作线程开始工作的时刻,最小值、平均值、最大值、差异(Max-Min)。
      [GC Worker Start (ms): Min: 2807.7, Avg: 2807.8, Max: 2807.8, Diff: 0.1]
	  // Ext Root Scanning (ms):表示扫描外部根(如全局变量、JNI引用等)的时间。
      [Ext Root Scanning (ms): Min: 0.3, Avg: 0.6, Max: 0.8, Diff: 0.5, Sum: 2.2]
	  // Update RS (ms):更新 Remembered Set(RS,记录对象引用关系的数据结构)的时间。
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
	  // Scan RS (ms): 扫描 Remembered Set 的时间。
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
	  // Code Root Scanning (ms):扫描代码根(如类元数据、方法表等)的时间。
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
	  // Object Copy (ms):复制存活对象到新的内存区域的时间。
      [Object Copy (ms): Min: 0.9, Avg: 1.2, Max: 1.4, Diff: 0.5, Sum: 4.6]
	  // Termination (ms):各工作线程完成任务后的终止协调时间。
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Termination Attempts: Min: 1, Avg: 2.5, Max: 4, Diff: 3, Sum: 10]
	  // GC Worker Other (ms):除上述阶段外的其他工作时间。
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
	  // GC Worker Total (ms): 每个工作线程的总工作时间。
      [GC Worker Total (ms): Min: 1.7, Avg: 1.8, Max: 1.8, Diff: 0.1, Sum: 7.1]
	  // GC Worker End (ms):各工作线程结束工作的时刻。
      [GC Worker End (ms): Min: 2809.5, Avg: 2809.5, Max: 2809.5, Diff: 0.0]
   // Code Root Fixup:修复代码根引起耗时。
   [Code Root Fixup: 0.0 ms]
   // Code Root Purge:清理代码根耗时。
   [Code Root Purge: 0.0 ms]
   // Clear CT:清除 Card Table(记录堆中哪些区域可能含有跨代引用)耗时。
   [Clear CT: 0.1 ms]
   // Other:其他操作(如CSet选择、引用处理等)的总耗时,详细列出各项操作的具体耗时。
   [Other: 1.0 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.8 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   /** 堆内存变化 */
   // Eden区:GC前已使用空间6144.0KB,总容量也为6144.0KB;GC后已使用空间清零,总容量变为12.0MB。
   // Survivors区:GC前已使用空间为0B,GC后已使用空间变为1024.0KB。
   // 整个堆:GC前已使用空间6144.0KB,总容量126.0MB;GC后已使用空间1520.0KB,总容量不变。
   [Eden: 6144.0K(6144.0K)->0.0B(12.0M) Survivors: 0.0B->1024.0K Heap: 6144.0K(126.0M)->1520.0K(126.0M)]
 /** 操作系统统计 */
 // user:用户态CPU时间,即垃圾收集过程中花费在用户态代码上的时间。
 // sys:内核态CPU时间,即垃圾收集过程中花费在内核态代码上的时间。
 // real:实际流逝时间(wall clock time),即从垃圾收集开始到结束的真实时间。
 [Times: user=0.00 sys=0.00, real=0.00 secs] 

注意: G1虽然在物理上取消了新生代和老年代的区域划分,但是逻辑上依然保留了,所以日志中还会显示 young,Full GC 会用 mixed 来表示。


三、GC 发生的原因

在 Java 中,GC 是由 JVM 自动完成的,根据 JVM 系统环境而定,所以时机是不确定的。当然,我们可以手动进行垃圾回收,比如调用 System.gc() 方法通知 JVM 进行一次垃圾回收,但是具体什么时刻运行也无法控制。也就是说 System.gc() 只是通知要回收,什么时候回收由 JVM 决定。

注意: 可能有些人会以为方法区是不会发生垃圾回收的,其实方法区也是会发生垃圾回收的,只不过大部分情况下,方法区发生垃圾回收之后效率不是很高,大部分内存都回收不掉,所以我们一般讨论垃圾回收的时候也只讨论堆内的回收。

一般有以下 5 种导致 GC 发生的原因:

3.1 Allocation Failure:新生代空间不足,触发 Minor GC

GC 日志如下:

2022-01-11T17:51:35.992-0800: 47.713: [GC (Allocation Failure) [PSYoungGen: 1280509K->89599K(1308160K)] 1396384K->217194K(1509376K), 0.0251936 secs] [Times: user=0.08 sys=0.01, real=0.02 secs]

Minor GC 触发的原因:新生代空间不足。

3.2 Metadata GC Threshold:元数据(方法区)空间不足,触发 Full GC

GC 日志如下:

2022-01-11T17:54:45.790-0800: 4.307: [Full GC (Metadata GC Threshold) [PSYoungGen: 12761K->0K(497664K)] [ParOldGen: 15911K->18963K(108032K)] 28672K->18963K(605696K), [Metaspace: 34603K->34603K(1081344K)], 0.0401502 secs] [Times: user=0.16 sys=0.00, real=0.04 secs]

Full GC 触发的原因:元空间(方法区)空间不足。

3.3 Ergonomics:系统调用,触发 Full GC

GC 日志如下:

2022-01-11T17:54:53.979-0800: 12.496: [Full GC (Ergonomics) [PSYoungGen: 49151K->0K(1077760K)] [ParOldGen: 91750K->95410K(221184K)] 140902K->95410K(1298944K), [Metaspace: 53259K->53259K(1097728K)], 0.1806514 secs] [Times: user=0.96 sys=0.01, real=0.19 secs]

Full GC 触发的原因:JVM为了优化系统性能和资源使用而自动发起的,JVM需要自动调节 GC 暂停时间和吞吐量之间的平衡。

3.4 System.gc():手动调用,触发 Full GC

GC 日志如下:

2023-0½-31T12:34:56.789+0000: 123.456: [GC (System.gc()) [PSYoungGen: 4096K->1024K(10240K)] .jpg0K->5120K(20480K), 0.0013400 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Full GC 触发的原因:程序调用了 System.gc() 函数。

注意: 当使用 System.gc() 时,它会建议(而不是强制)Java 虚拟机(JVM)执行一次垃圾回收。具体的垃圾收集类型取决于 JVM 的配置和当前的内存状态,不一定都是 Full GC。


四、实战:项目启动时速度很慢

当项目启动的时候,启动耗时特别长,这时打印 GC 日志如下:

在这里插入图片描述

其中,第一次 Full GC 及之前的 Young GC 日志如下:

2022-01-12T11:05:59.658-0800: 1.334: [GC (Allocation Failure) [PSYoungGen: 65536K->4358K(76288K)] 65536K->4374K(251392K), 0.0196609 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 

2022-01-12T11:06:00.062-0800: 1.738: [GC (Allocation Failure) [PSYoungGen: 69894K->5059K(141824K)] 69910K->5147K(316928K), 0.0060396 secs] [Times: user=0.02 sys=0.01, real=0.00 secs] 

2022-01-12 11:06:00,193 main ERROR Console contains an invalid element or attribute "append"
  
2022-01-12T11:06:00.886-0800: 2.561: [GC (Allocation Failure) [PSYoungGen: 136131K->10741K(141824K)] 136219K->11359K(316928K), 0.0159439 secs] [Times: user=0.03 sys=0.01, real=0.02 secs] 

2022-01-12T11:06:00.958-0800: 2.634: [GC (Metadata GC Threshold) [PSYoungGen: 23884K->7144K(272896K)] 24502K->7771K(448000K), 0.0063136 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 

2022-01-12T11:06:00.965-0800: 2.640: [Full GC (Metadata GC Threshold) [PSYoungGen: 7144K->0K(272896K)] [ParOldGen: 626K->7038K(88576K)] 7771K->7038K(361472K), [Metaspace: 20521K->20520K(1067008K)], 0.0280148 secs] [Times: user=0.10 sys=0.01, real=0.03 secs]

可以看到,当项目启动 1.3s 的时候,便开始触发 Minor GC 了。因为项目刚刚启动的时候,要加载很多类,随后 2.640 的时候便触发了一次 Full GC,触发的原因是元数据空间不足。元数据空间(20521K->20520K(1067008K))消耗了 20M 了,垃圾回收之后,元数据空间基本上没有被回收,因为元数据保存的是类信息。我们知道 元数据默认空间大小是21M,如果空间不足会触发 Full GC,然后扩容

第二次 Full GC 日志如下:

2022-01-12T11:06:04.538-0800: 6.214: [Full GC (Metadata GC Threshold) [PSYoungGen: 11694K->0K(466944K)] [ParOldGen: 13317K->17995K(115200K)] 25011K->17995K(582144K), [Metaspace: 34079K->34079K(1079296K)], 0.0563024 secs] [Times: user=0.15 sys=0.00, real=0.06 secs]

在项目启动的第 6s,再次触发了 Full GC,原因也是元数据空间不足。这次元数据空间(34079K->34079K(1079296K))消耗了 34M,垃圾回收完毕以后,也是基本没被回收。但是我们可以看出,元数据空间扩容了,从 21M 扩容到了 34M。

结合上面两次 Full GC 的情况来看,为了避免元数据空间扩容导致频繁 Full GC,我们可以在项目启动的时候提前设置好元数据空间的大小:

-XX:+MetaspaceSize=256M -XX:MaxMetaspaceSize=256M

我们重新配置参数,启动项目,可以看到 Full GC 的次数变少了,完整 JVM 配置参数如下所示:

‐Xloggc:./gc‐adjust‐%t.log -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M ‐XX:+PrintGCDetails -XX:+Print GCDateStamps -XX:+PrintGCTimeStamps ‐XX:+PrintGCCause ‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100M 

整理完毕,完结撒花~🌻





参考地址:

1.教你如何通过分析GC日志来进行JVM调优,https://cloud.tencent.com/developer/article/1745971
2.20.GC日志详解及日志分析工具,https://www.cnblogs.com/ITPower/p/15793047.html
3.GC日志解读,这次别再说看不懂GC日志了,https://juejin.cn/post/7029130033268555807
4.GC 日志分析,https://blog.csdn.net/chengqiuming/article/details/119292491
5.【JVM系列5】深入分析Java垃圾收集算法和常用垃圾收集器,https://blog.csdn.net/zwx900102/article/details/108180739

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1614472.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

poll实现echo服务器的并发

poll实现echo服务器的并发 代码实现 #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/time.h> #include <unistd.h> #…

c++的智能指针(5) -- weak_ptr

概述 我们在使用shared_ptr会出现以下的问题&#xff0c;会导致内存泄露。 代码1: 类内指针循环指向 #include <iostream> #include <memory>class B;class A { public:A() {std::cout << "Construct" << std::endl;}~A() {std::cout <…

基于开源CrashRpt与微软开源Detours技术深度改造的异常捕获库分享

目录 1、异常捕获模块概述 2、为什么需要异常捕获模块&#xff1f; 3、在有些异常的场景下是没有生成dump文件的 4、开源异常捕获库CrashRpt介绍 5、对开源库CrashRpt的改进 C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持…

# 从浅入深 学习 SpringCloud 微服务架构(二)模拟微服务环境(1)

从浅入深 学习 SpringCloud 微服务架构&#xff08;二&#xff09;模拟微服务环境&#xff08;1&#xff09; 段子手168 1、打开 idea 创建父工程 创建 artifactId 名为 spring_cloud_demo 的 maven 工程。 --> idea --> File --> New --> Project --> Ma…

基于贝叶斯算法的机器学习在自动驾驶路径规划中的应用实例

目录 第一章 引言 第二章 数据准备 第三章 贝叶斯路径规划模型训练 第四章 路径规划预测 第五章 路径执行 第六章 实验结果分析 第一章 引言 自动驾驶技术的发展带来了自动驾驶车辆的出现&#xff0c;而路径规划作为自动驾驶车辆的关键功能之一&#xff0c;对于确定最佳行…

锐捷校园网自助服务系统 operatorReportorRoamService SQL注入漏洞致RCE漏洞复现

0x01 产品简介 锐捷校园网自助服务系统是锐捷网络推出的一款面向学校和校园网络管理的解决方案。该系统旨在提供便捷的网络自助服务,使学生、教职员工和网络管理员能够更好地管理和利用校园网络资源。 0x02 漏洞概述 锐捷校园网自助服务系统 operatorReportorRoamService 接…

STP学习的第一篇

1.STP的基本概念&#xff1a;根桥 &#xff08;1&#xff09;STP的主要作用之一是在整个交换网络中计算出一棵无环的“树”&#xff08;STP树&#xff09;。 &#xff08;2&#xff09;根桥是一个STP交换网络中的“树根”。 &#xff08;3&#xff09;STP开始工作后&#xf…

一、MinIO基本知识

MinIO基本知识 一、简介1.许可 二、部署1.Docker部署1.1 部署容器 1.2 MinIO页面访问1.3 创建Bucket![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6c8aa92975f146b691f1f36ce1033e7c.png) 三、Python-API1.安装包2.Bucket、Object概念3.Bucket-API4.MinIOClient-…

【Yolov系列】Yolov5学习(一)补充1.2:自适应锚框计算详解+代码注释

一、自适应锚框计算详解 自适应锚框计算的具体过程&#xff1a; ①获取数据集中所有目标的宽和高。 ②将每张图片中按照等比例缩放的方式到 resize 指定大小&#xff0c;这里保证宽高中的最大值符合指定大小。 ③将 bboxes 从相对坐标改成绝对坐标&#xff0c;这里…

余氯控制器的功能优势简介

余氯控制器是一款智能化的水质监测设备&#xff0c;它采用高精度AD转换和单片机微处理技术&#xff0c;能够完成余氯值的高精度测量。这款控制器具备时间显示、数据存储等基本功能。 高智能化设计&#xff1a;余氯控制器采用了高精度AD转换和单片机微处理技术&#xff0c;确保…

VisualGLM-6B的部署步骤

对于如下命令&#xff0c;你将完全删除环境和环境中的所有软件包 conda remove -n env_name --all 一、VisualGLM-6B环境安装 1、硬件配置 操作系统&#xff1a;Ubuntu_64&#xff08;ubuntu22.04.3&#xff09; GPU&#xff1a;4050 显存&#xff1a;16G 2、配置环境 建…

防水型RTU IP68防水遥测终端机

在工业物联网的领域中&#xff0c;防水型RTU(Remote Terminal Unit)具有不可或缺的重要性。作为工业设备的守护神&#xff0c;它在实现数据采集和传输、远程控制和预警告警的同时&#xff0c;还能保障设备免受水分侵害&#xff0c;确保系统稳定安全的运行。    计讯物联防水…

JDK 11下载、安装、配置

下载 到Oracle管网下载JDK 11&#xff0c;下载前需要登录&#xff0c;否则直接点下载会出现502 bad gateway。 下载页面链接 https://www.oracle.com/hk/java/technologies/downloads/#java11-windows 登录 有些人可能没有Oracle账号&#xff0c;注册也比较慢&#xff0c;有需…

2024_GAMES101作业环境配置Mac(intel)_VSCode_Clion

目录 VSCodeClionCMakeList.txt VSCode brew install cmake 更换下载源为阿里云下载 opencv&#xff0c;不然会很慢 cd "$(brew --repo)" git remote -v cd "$(brew --repo)" git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git…

Python --- 基于Iris flower数据集的kNN分类实战

基于Iris flower数据集的kNN分类实战 Iris data set(鸢尾花数据集简介) 鸢尾花数据集共包含三种鸢尾花&#xff1a;Iris setosa, Iris virginica and Iris versicolor。 Iris setosa&#xff08;山鸢尾&#xff09; Iris virginica&#xff08;维吉尼亚鸢尾 &#xff09; Iris …

本地环境测试

1. 在 Anaconda Navigator 中&#xff0c;打开 Jupyter Notebook &#xff0c;在网页中&#xff0c;点击进入本地环境搭建中创 建的工作目录&#xff0c;点击右上角的 New- 》 Folder &#xff0c;将新出现的 Untitled Folder 选中&#xff0c;并使用左上角 的 Rename 按钮重…

C++ //练习 12.30 定义你自己版本的TextQuery和QueryResult类,并执行12.3.1节(第431页)中的runQueries函数。

C Primer&#xff08;第5版&#xff09; 练习 12.30 练习 12.30 定义你自己版本的TextQuery和QueryResult类&#xff0c;并执行12.3.1节&#xff08;第431页&#xff09;中的runQueries函数。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1…

{“errMsg“:“insertXWebCamera:fail appid privacy api banned“}

问题描述&#xff1a;微信小程序&#xff0c;在体验版本测试时&#xff0c;调用摄像头OK&#xff0c;没有任何问题&#xff0c;部署发布版本后&#xff0c;日志报错内容&#xff1a;{"errMsg":"insertXWebCamera:fail appid privacy api banned"}&#xff…

opencv人脸打马赛克

import cv2def FaceFind(imgPath: str) -> list:image cv2.imread(imgPath)gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)face_cascade cv2.CascadeClassifier(haarcascade_frontalface_default.xml)# 返回人脸坐标列表faces face_cascade.detectMultiScale(gray, scal…

MultiCD工具:创建一个多引导Linux USB驱动器

众所周知&#xff0c;拥有一个可安装多个可用操作系统的 CD 或 USB 驱动器在各种情况下都非常有用。无论是为了快速测试或调试某些内容&#xff0c;还是只是重新安装笔记本电脑或 PC 的操作系统&#xff0c;这都可以为你节省大量时间。 在本文中&#xff0c;将介绍如何使用名为…