如何确定我在使用哪个垃圾回收器?

8
我没有指定任何GC,而且我认为我的JVM默认情况下没有启用任何GC。
当然,我知道OpenJDK8默认使用ParallelGC,但我认为它应该可以通过命令行打印出来,就像这样: java -XX:+PrintFlagsFinal|grep Use|grep GC 我希望输出包含bool UseParallelOldGC = true {product},但实际上并没有:
     bool ParGCUseLocalOverflow                     = false                               {product}
     bool UseAdaptiveGCBoundary                     = false                               {product}
     bool UseAdaptiveSizeDecayMajorGCCost           = true                                {product}
     bool UseAdaptiveSizePolicyWithSystemGC         = false                               {product}
     bool UseAutoGCSelectPolicy                     = false                               {product}
     bool UseConcMarkSweepGC                        = false                               {product}
     bool UseDynamicNumberOfGCThreads               = false                               {product}
     bool UseG1GC                                   = false                               {product}
     bool UseGCLogFileRotation                      = false                               {product}
     bool UseGCOverheadLimit                        = true                                {product}
     bool UseGCTaskAffinity                         = false                               {product}
     bool UseMaximumCompactionOnSystemGC            = true                                {product}
     bool UseParNewGC                               = false                               {product}
     bool UseParallelGC                             = false                               {product}
     bool UseParallelOldGC                          = false                               {product}
     bool UseSerialGC                               = false                               {product}

并且

java -XX:+PrintCommandLineFlags -version

我期望输出结果包含:XX:+UseParallelGC 但实际上并没有:

-XX:InitialHeapSize=460493056 -XX:MaxHeapSize=7367888896 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
.

我的JVM选项:

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -verbose:gc -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime
.

上面的输出表明没有任何垃圾回收被启用(我认为是这样),这种情况让我很困惑。
GC日志输出如下:
OpenJDK 64-Bit Server VM (25.181-b13) for linux-amd64 JRE (1.8.0_181-b13), built on Oct 23 2018 11:39:12 by "buildozer" with gcc 6.4.0
Memory: 4k page, physical 28780816k(6283132k free), swap 0k(0k free)
CommandLine flags: -XX:InitialHeapSize=460493056 -XX:MaxHeapSize=7367888896 -XX:+PrintGC -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDeta
ils -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
{Heap before GC invocations=0 (full 0):
 def new generation   total 135168K, used 120192K [0x0000000608c00000, 0x0000000611ea0000, 0x000000069b2a0000)
  eden space 120192K, 100% used [0x0000000608c00000, 0x0000000610160000, 0x0000000610160000)
  from space 14976K,   0% used [0x0000000610160000, 0x0000000610160000, 0x0000000611000000)
  to   space 14976K,   0% used [0x0000000611000000, 0x0000000611000000, 0x0000000611ea0000)
 tenured generation   total 300416K, used 0K [0x000000069b2a0000, 0x00000006ad800000, 0x00000007c0000000)
   the space 300416K,   0% used [0x000000069b2a0000, 0x000000069b2a0000, 0x000000069b2a0200, 0x00000006ad800000)
 Metaspace       used 20532K, capacity 20780K, committed 20992K, reserved 1067008K
  class space    used 2628K, capacity 2726K, committed 2816K, reserved 1048576K
2019-02-25T06:29:46.105+0000: 2.890: [GC (Allocation Failure) 2019-02-25T06:29:46.105+0000: 2.890: [DefNew
Desired survivor size 7667712 bytes, new threshold 1 (max 15)
- age   1:   15335424 bytes,   15335424 total
: 120192K->14976K(135168K), 0.0238110 secs] 120192K->18041K(435584K), 0.0238765 secs] [Times: user=0.01 sys=0.01, real=0.02 secs] 
Heap after GC invocations=1 (full 0):
 def new generation   total 135168K, used 14976K [0x0000000608c00000, 0x0000000611ea0000, 0x000000069b2a0000)
  eden space 120192K,   0% used [0x0000000608c00000, 0x0000000608c00000, 0x0000000610160000)
  from space 14976K, 100% used [0x0000000611000000, 0x0000000611ea0000, 0x0000000611ea0000)
  to   space 14976K,   0% used [0x0000000610160000, 0x0000000610160000, 0x0000000611000000)
 tenured generation   total 300416K, used 3065K [0x000000069b2a0000, 0x00000006ad800000, 0x00000007c0000000)
   the space 300416K,   1% used [0x000000069b2a0000, 0x000000069b59e660, 0x000000069b59e800, 0x00000006ad800000)
 Metaspace       used 20532K, capacity 20780K, committed 20992K, reserved 1067008K
  class space    used 2628K, capacity 2726K, committed 2816K, reserved 1048576K
}
2019-02-25T06:29:46.129+0000: 2.914: Total time for which application threads were stopped: 0.0241189 seconds, Stopping threads took: 0.0000181 seconds
{Heap before GC invocations=1 (full 0):
 def new generation   total 135168K, used 21860K [0x0000000608c00000, 0x0000000611ea0000, 0x000000069b2a0000)
  eden space 120192K,   5% used [0x0000000608c00000, 0x00000006092b93f8, 0x0000000610160000)
  from space 14976K, 100% used [0x0000000611000000, 0x0000000611ea0000, 0x0000000611ea0000)
  to   space 14976K,   0% used [0x0000000610160000, 0x0000000610160000, 0x0000000611000000)
 tenured generation   total 300416K, used 3065K [0x000000069b2a0000, 0x00000006ad800000, 0x00000007c0000000)
   the space 300416K,   1% used [0x000000069b2a0000, 0x000000069b59e660, 0x000000069b59e800, 0x00000006ad800000)
 Metaspace       used 20982K, capacity 21132K, committed 21248K, reserved 1067008K
  class space    used 2667K, capacity 2758K, committed 2816K, reserved 1048576K
2019-02-25T06:29:46.187+0000: 2.972: [Full GC (Metadata GC Threshold) 2019-02-25T06:29:46.187+0000: 2.972: [Tenured: 3065K->9617K(300416K), 0.0270556 secs] 24926K-
>9617K(435584K), [Metaspace: 20982K->20982K(1067008K)], 0.0271334 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
Heap after GC invocations=2 (full 1):
 def new generation   total 135296K, used 0K [0x0000000608c00000, 0x0000000611ec0000, 0x000000069b2a0000)
  eden space 120320K,   0% used [0x0000000608c00000, 0x0000000608c00000, 0x0000000610180000)
  from space 14976K,   0% used [0x0000000610180000, 0x0000000610180000, 0x0000000611020000)
  to   space 14976K,   0% used [0x0000000611020000, 0x0000000611020000, 0x0000000611ec0000)
 tenured generation   total 300416K, used 9617K [0x000000069b2a0000, 0x00000006ad800000, 0x00000007c0000000)
   the space 300416K,   3% used [0x000000069b2a0000, 0x000000069bc04698, 0x000000069bc04800, 0x00000006ad800000)
 Metaspace       used 20982K, capacity 21132K, committed 21248K, reserved 1067008K
  class space    used 2667K, capacity 2758K, committed 2816K, reserved 1048576K
}

(从应用程序启动到第一次重要GC)

日志显示JVM堆被分为新生代(new)年老代(tenured),但不会打印GC类型。

遗憾的是,在我的环境中,我也无法使用jmap -heap {pid}来获取GC类型,因为jmap没有这个选项(-heap)。

所以我想知道:

  1. 我真正使用的是哪种GC?
  2. 命令行选项(-XX:+PrintCommandLineFlags-XX:+PrintFlagsFinal)输出的信息是否正确?

我的环境: k8s+docker,Alpine OpenJKD8


我没有指定任何GC,我认为我的JVM默认没有启用任何GC。不可能。Java 11中添加了一个无操作垃圾收集器,但它不是默认选项。 - Michael
是的,垃圾回收器正在工作。所以我很困惑:为什么我不能打印出默认的垃圾回收器,以及我真正使用的是哪个垃圾回收器? - lianej
2个回答

9

jcmd <pid> VM.flags 应该有所帮助。

例如:

$ /usr/java/jdk1.8.0_202/bin/java Test
...
$ jcmd 28815 VM.flags
28815:
-XX:CICompilerCount=3 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4257218560 -XX:MaxNewSize=1418723328 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC

即使没有明确指定,最后打印的标志是-XX:+UseParallelGC

然后,如果我运行默认使用G1收集器的JDK 11,我将得到以下输出:

$ /usr/java/jdk11.0.2/bin/java Test
...
$ jcmd 28862 VM.flags
28862:
-XX:CICompilerCount=3 -XX:ConcGCThreads=1 -XX:G1ConcRefinementThreads=4 -XX:G1HeapRegionSize=1048576 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=266338304 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=4257218560 -XX:MaxNewSize=2554331136 -XX:MinHeapDeltaBytes=1048576 -XX:NonNMethodCodeHeapSize=5830092 -XX:NonProfiledCodeHeapSize=122914074 -XX:ProfiledCodeHeapSize=122914074 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC

现在最后一个打印选项是-XX:+UseG1GC

如果由于某种原因您没有可用的jcmd,您可以尝试jattach,它是jcmd的轻量级独立替代品:

$ jattach 28862 jcmd VM.flags

更新

如果没有打印-XX:Use * GC标志,则表示使用Serial GC。这仅在JDK 8及更早版本上发生。从JDK 9开始,JVM会自动设置其中一个 Use * GC 标志。有关详细信息,请参见JDK-8068582


谢谢,jcmd可用,但在我的情况下,它没有打印GC类型。 - lianej
jcmd <pid> VM.flags -all 将打印所有选项,包括 GC 详细信息。 - Andbdrew

7
问题在于你正在查看类型为 bool 的JVM选项,而实际上你需要的是一个名称。这不是你的错,JVM的设计者决定给不同的垃圾收集器命名,但提供了看起来像布尔选项的控制项。
因此,即使所有这些选项都是 false,也存在一个垃圾收集器,但这些选项不足以获取其名称。但是另一方面,大多数名称都无法描述这些垃圾收集器所做的事情,或者它们与其他算法的区别在哪里。
JDK8 默认情况下使用 ParallelGC 并不完全正确;正如这个答案所描述的那样,该算法是通过某种启发式方法选择的,尽管在大多数情况下,你最终会到达 ParallelGC 。
请使用以下代码:
Object flags = ManagementFactory.getPlatformMBeanServer().invoke(
    ObjectName.getInstance("com.sun.management:type=DiagnosticCommand"),
    "vmFlags", new Object[] { null }, new String[] { "[Ljava.lang.String;" });
for(String f: ((String)flags).split("\\s+"))
    if(f.contains("GC")) System.out.println(f);
for(GarbageCollectorMXBean gc: ManagementFactory.getGarbageCollectorMXBeans())
    System.out.printf("%-20s%s%n", gc.getName(), Arrays.toString(gc.getMemoryPoolNames()));

我理解

> jdk1.8.0_162\bin\java ...
-XX:+UseParallelGC
PS Scavenge         [PS Eden Space, PS Survivor Space]
PS MarkSweep        [PS Eden Space, PS Survivor Space, PS Old Gen]

在我的机器上,没有设置选项时会使用ParallelGC作为默认的垃圾回收算法。但请注意报告的“PS Scavenge”和“PS MarkSweep”名称,这凸显出选项和名称的另一个问题:典型的配置考虑使用两种垃圾回收算法,一种用于minor gc,一种用于major gc。
当我尝试使用-XX:-UseParallelGC选项时,会得到以下结果。
> jdk1.8.0_162\bin\java -XX:-UseParallelGC ...
-XX:+UseParallelGC
PS Scavenge         [PS Eden Space, PS Survivor Space]
PS MarkSweep        [PS Eden Space, PS Survivor Space, PS Old Gen]

下面是关于JVM选项看起来像boolean的问题的解释:我无法将它们关闭,因为JVM需要选择另一个实际存在的垃圾收集器。
因此,要关闭并行处理,您可以使用-XX:+UseSerialGC

> jdk1.8.0_162\bin\java -XX:+UseSerialGC ...
-XX:+UseSerialGC
Copy                [Eden Space, Survivor Space]
MarkSweepCompact    [Eden Space, Survivor Space, Tenured Gen]

用于比较

> jdk1.8.0_162\bin\java -XX:+UseConcMarkSweepGC ...
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
ParNew              [Par Eden Space, Par Survivor Space]
ConcurrentMarkSweep [Par Eden Space, Par Survivor Space, CMS Old Gen]

请注意,这两个算法中的每一个都可以与一个选项相关联,但指定一个选项可能会选择两个垃圾回收算法。
> jdk-9.0.4\bin\java ...
-XX:ConcGCThreads=2
-XX:+UseG1GC
G1 Young Generation [G1 Eden Space, G1 Survivor Space]
G1 Old Generation   [G1 Eden Space, G1 Survivor Space, G1 Old Gen]

> jdk-11.0.1\bin\java ...
-XX:ConcGCThreads=2
-XX:GCDrainStackTargetSize=64
-XX:+UseG1GC
G1 Young Generation [G1 Eden Space, G1 Survivor Space, G1 Old Gen]
G1 Old Generation   [G1 Eden Space, G1 Survivor Space, G1 Old Gen]

> jdk-11.0.1\bin\java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC ...
-XX:+UseEpsilonGC
Epsilon Heap        [Epsilon Heap]

因此,如果代码试图获取关联的Use…GC boolean选项(即上面代码的第一部分,使用非标准的com.sun.management:type=DiagnosticCommand MBean)未找到任何内容,则可以尝试使用由getGarbageCollectorMXBeans()报告的垃圾收集器名称,但是正如您所看到的,这些名称与JVM选项的名称不匹配,因此您必须了解这些名称是如何关联的。
但最终,这些名称都不是真正描述性的,因此它们只有在您已经知道这些名称背后的内容时才有用。

非常感谢!我的应用程序使用Copy + MarkSweepCompact,可能是SerialGC。 - lianej

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接