如何让 G1 执行定期的完全垃圾回收?

huangapple go评论73阅读模式
英文:

How to make G1 do periodic full GCs?

问题

我们的Java应用程序分配的内存(使用top进行验证)随着时间的推移不断增长,达到了8 GB甚至更多。

当进行堆转储以查找是否存在内存泄漏时,我注意到在进行转储后,内存分配大幅减少。原因在于gc日志中。请看这里的第二行:

[781944.122s][info][gc] GC(412) Pause Young (Normal) (G1 Evacuation Pause) 3948M->1021M(5256M) 38.430ms
[782877.123s][info][gc] GC(413) Pause Full (Heap Dump Initiated GC) 3929M->471M(1712M) 200.948ms

因此,我的想法是使G1定期进行完整的堆转储(仅用于测试的1分钟间隔),如下所示:

java -XX:G1PeriodicGCInterval=60000 -XX:G1PeriodicGCSystemLoadThreshold=0.9 ...

现在当我检查日志时,当满足条件时,定期的GC会执行,但它们不是完整的GC:

[240.042s][info][gc] GC(12) Pause Young (Concurrent Start) (G1 Periodic Collection) 405M->404M(2328M) 16.047ms
[240.042s][info][gc] GC(13) Concurrent Mark Cycle
[240.343s][info][gc] GC(13) Pause Remark 404M->388M(1360M) 2.564ms
[240.479s][info][gc] GC(13) Pause Cleanup 389M->389M(1360M) 0.106ms
[240.485s][info][gc] GC(13) Concurrent Mark Cycle 442.918ms

[300.043s][info][gc] GC(14) Pause Young (Prepare Mixed) (G1 Periodic Collection) 392M->386M(1360M) 0.882ms

[360.047s][info][gc] GC(15) Pause Young (Mixed) (G1 Periodic Collection) 390M->378M(1360M) 1.824ms

[420.049s][info][gc] GC(16) Pause Young (Concurrent Start) (G1 Periodic Collection) 383M->378M(1360M) 1.144ms
[420.049s][info][gc] GC(17) Concurrent Mark Cycle
[420.382s][info][gc] GC(17) Pause Remark 378M->378M(1360M) 1.080ms
[420.489s][info][gc] GC(17) Pause Cleanup 378M->378M(1360M) 0.105ms
[420.495s][info][gc] GC(17) Concurrent Mark Cycle 445.553ms

G1调优文档中指出:
> -XX:+G1PeriodicGCInvokesConcurrent
>
> 如果设置,定期的垃圾收集将触发并发标记或继续现有的收集周期,否则触发完全GC。

由于我没有设置G1PeriodicGCInvokesConcurrent,我预期会执行完全的GC。我漏掉了什么?

注:这是Java 17:

$ java -version
openjdk version "17.0.7" 2023-04-18 LTS
OpenJDK Runtime Environment Corretto-17.0.7.7.1 (build 17.0.7+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.7.7.1 (build 17.0.7+7-LTS, mixed mode, sharing)
英文:

The allocated memory of our Java app (as verified using top) continuously grows as days pass, to 8 GB and more.

When taking a heap dump to figure out whether there was a leak like so:

jmap -dump:live,format=b,file=/tmp/myapp.hprof 17393

I noticed that after taking the dump, memory allocation shrunk considerably. The reason was found in the gc log. See the second line here:

[781944.122s][info][gc] GC(412) Pause Young (Normal) (G1 Evacuation Pause) 3948M->1021M(5256M) 38.430ms
[782877.123s][info][gc] GC(413) Pause Full (Heap Dump Initiated GC) 3929M->471M(1712M) 200.948ms

So my idea was to make G1 do a full heap dump periodically (1 minute interval for testing only), like so:

java -XX:G1PeriodicGCInterval=60000 -XX:G1PeriodicGCSystemLoadThreshold=0.9 ...

Now when I check the logs, periodic GCs are being done when conditions are met, but they aren't full GCs:

[240.042s][info][gc] GC(12) Pause Young (Concurrent Start) (G1 Periodic Collection) 405M->404M(2328M) 16.047ms
[240.042s][info][gc] GC(13) Concurrent Mark Cycle
[240.343s][info][gc] GC(13) Pause Remark 404M->388M(1360M) 2.564ms
[240.479s][info][gc] GC(13) Pause Cleanup 389M->389M(1360M) 0.106ms
[240.485s][info][gc] GC(13) Concurrent Mark Cycle 442.918ms

[300.043s][info][gc] GC(14) Pause Young (Prepare Mixed) (G1 Periodic Collection) 392M->386M(1360M) 0.882ms

[360.047s][info][gc] GC(15) Pause Young (Mixed) (G1 Periodic Collection) 390M->378M(1360M) 1.824ms

[420.049s][info][gc] GC(16) Pause Young (Concurrent Start) (G1 Periodic Collection) 383M->378M(1360M) 1.144ms
[420.049s][info][gc] GC(17) Concurrent Mark Cycle
[420.382s][info][gc] GC(17) Pause Remark 378M->378M(1360M) 1.080ms
[420.489s][info][gc] GC(17) Pause Cleanup 378M->378M(1360M) 0.105ms
[420.495s][info][gc] GC(17) Concurrent Mark Cycle 445.553ms

The G1 tuning docs state:
> -XX:+G1PeriodicGCInvokesConcurrent
>
> If set, periodic garbage collections trigger a concurrent marking or continue the existing collection cycle, otherwise trigger a Full GC.

As I am not setting G1PeriodicGCInvokesConcurrent, I would expect full GCs. What am I missing?

N.B. This is Java 17:

$ java -version
openjdk version "17.0.7" 2023-04-18 LTS
OpenJDK Runtime Environment Corretto-17.0.7.7.1 (build 17.0.7+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.7.7.1 (build 17.0.7+7-LTS, mixed mode, sharing)

答案1

得分: 1

你没有内存泄漏,否则它将无法完全收缩,收集器中将会出现内存不足错误和/或高CPU使用率。

你不应该尝试更改垃圾收集器的默认配置。增加完全GC的次数将无故影响性能。垃圾收集器算法很复杂,只有在需要时才会运行完全GC。你提到的情况都没有表明有这样的需要。Java GC算法的大多数改进都是为了减少完全GC的需求,从而提升性能。更改配置需要对每个配置项有充分的了解,并对每个更改的性能进行全面测试。

相反,要分析你获取的堆转储文件,以了解可能正在使用该内存的内容。请注意,堆转储分析需要理解Java堆内存的对象和引用使用情况。

英文:

You don't have a memory leak, otherwise it would not fully shrunk and you would have an Out of Memory error and/or high CPU usage in the collector.

You should not try to change the default configuration of the garbage collector. Increasing the number of full GCs will impact performance for no reason or gain. The garbage collector algorithm is complex and runs full GC only if it is needed. Nothing that you mention indicates a need. Most improvements in Java GC algorithms are to reduce the need for full GC, which impacts performance. Changing the configurations needs to be done with understanding of each configuration and fully testing performance of each change.

Instead analyze the heap dumps you are taking to understand what may be using that memory. Note that heap dump analysis requires understanding of Java heap memory use by objects and references.

答案2

得分: 0

Thanks @StephenC对于对我的问题的评论!我尝试了-XX:+PrintFlagsFinal,并且它确认了您的怀疑:

$ journalctl -u myapp|grep Periodic
 6月 09 11:48:27 filinux01 start-nnd[86668]: uintx G1PeriodicGCInterval                     = 600000                                 {manageable} {command line}
 6月 09 11:48:27 filinux01 start-nnd[86668]: bool G1PeriodicGCInvokesConcurrent            = true                                      {product} {default}
 6月 09 11:48:27 filinux01 start-nnd[86668]: double G1PeriodicGCSystemLoadThreshold          = 0.900000                               {manageable} {command line}

因此,我将-XX:-G1PeriodicGCInvokesConcurrent添加到了jvm参数中,现在它按预期工作了:

[0.006s][info][gc] Using G1
[0.527s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 39M->13M(776M) 3.500ms
[12.049s][info][gc] GC(1) Pause Full (G1 Periodic Collection) 24M->13M(136M) 15.156ms
[18.075s][info][gc] GC(2) Pause Full (G1 Periodic Collection) 13M->13M(80M) 25.214ms
[24.098s][info][gc] GC(3) Pause Full (G1 Periodic Collection) 13M->13M(80M) 20.581ms
英文:

Thanks @StephenC for the comment to my question! I have tried -XX:+PrintFlagsFinal and it confirms your suspicion:

$ journalctl -u myapp|grep Periodic
 6月 09 11:48:27 filinux01 start-nnd[86668]: uintx G1PeriodicGCInterval                     = 600000                                 {manageable} {command line}
 6月 09 11:48:27 filinux01 start-nnd[86668]: bool G1PeriodicGCInvokesConcurrent            = true                                      {product} {default}
 6月 09 11:48:27 filinux01 start-nnd[86668]: double G1PeriodicGCSystemLoadThreshold          = 0.900000                               {manageable} {command line}

So I added -XX:-G1PeriodicGCInvokesConcurrent to the jvm arguments and now it works as expected:

[0.006s][info][gc] Using G1
[0.527s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 39M->13M(776M) 3.500ms
[12.049s][info][gc] GC(1) Pause Full (G1 Periodic Collection) 24M->13M(136M) 15.156ms
[18.075s][info][gc] GC(2) Pause Full (G1 Periodic Collection) 13M->13M(80M) 25.214ms
[24.098s][info][gc] GC(3) Pause Full (G1 Periodic Collection) 13M->13M(80M) 20.581ms

huangapple
  • 本文由 发表于 2023年6月9日 08:07:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76436415.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定