如何查看JVM使用的是什么垃圾收集器


封面


一、背景分析

无论讨论对象的内存分配,还是讨论内存的回收策略或是算法,都需要明确指出是基于哪种垃圾收集器进行的讨论。所以,很自然地就想到这个问题:如何查看 JVM 使用了哪种垃圾收集器呢

 


二、查看垃圾收集器

方法有很多,可以通过jconsole、VisualVM等工具间接地查看垃圾收集器的版本,但是比较简单的方式还是使用命令。

 
在cmd中执行下面的命令:

java -XX:+PrintCommandLineFlags -version

然后输出了如下内容:
在这里插入图片描述
从图中可以看出:

  • JDK版本:1.8.0
  • JVM是64位的Server版本
  • 结合下一张图的 参数说明,可知使用的垃圾收集器是 :新生代(Parallel Scavenge),老年代(PS MarkSweep)。这是虚拟机Server模式下的默认值。

在这里插入图片描述
图片来源:《深入理解Java虚拟机:JVM高级特性与最佳实践》

下面看一下jconsole的截图:
jconsole
 
启动jconsole的方法也很简单,只需要在命令行中输入: jconsole ,就可以启动该工具。

 


三、理解的偏差

但是,后来看到这位大哥的文章:
https://www.cnblogs.com/grey-wolf/p/9217497.html

才发现自己的理解是有偏差的。为什么这么说呢?
因为我写了一个验证GC的小demo,发现打印出的日志信息如下:

Heap
 PSYoungGen      total 9216K, used 3378K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 41% used [0x00000000ff600000,0x00000000ff94c880,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 10239K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 99% used [0x00000000fec00000,0x00000000ff5ffc10,0x00000000ff600000)
 Metaspace       used 3036K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 328K, capacity 388K, committed 512K, reserved 1048576K

相关代码如下:

public class MemoryAllocationDemo {
 
    /**
     * VM:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     */
    public static void main(String[] args) {
        //对象直接分配到老年代
        byte[] alloc = new byte[10239*1024];
    }
}

虚拟机参数如下:

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

参数说明:

  • 新生代:10M
  • 老年代:10M
  • 初始堆内存和最大堆内存:20M
  • Eden和Survivor的比例是:8:1(即Eden是8M,单个survivor是1M)

据说在jdk8中,默认启用了 ParallelOldGC ,相关代码如下:

--- a/src/share/vm/runtime/arguments.cpp	Mon Jan 30 15:21:57 2012 +0100
+++ b/src/share/vm/runtime/arguments.cpp	Thu Feb 02 16:05:17 2012 -0800
@@ -1400,10 +1400,11 @@
 
 void Arguments::set_parallel_gc_flags() {
   assert(UseParallelGC || UseParallelOldGC, "Error");
-  // If parallel old was requested, automatically enable parallel scavenge.
-  if (UseParallelOldGC && !UseParallelGC && FLAG_IS_DEFAULT(UseParallelGC)) {
-    FLAG_SET_DEFAULT(UseParallelGC, true);
+  // Enable ParallelOld unless it was explicitly disabled (cmd line or rc file).
+  if (FLAG_IS_DEFAULT(UseParallelOldGC)) {
+    FLAG_SET_DEFAULT(UseParallelOldGC, true);
   }
+  FLAG_SET_DEFAULT(UseParallelGC, true);
 
   // If no heap maximum was requested explicitly, use some reasonable fraction
   // of the physical memory, up to a maximum of 1GB.

相关修改来源:

https://hg.openjdk.org/jdk8u/jdk8u/hotspot/rev/24cae3e4cbaa

在这里插入图片描述
出于好奇,我在虚拟机参数中增加了如下内容:

-XX:+UseParallelOldGC -XX:-UseParallelGC

参数说明:

  • 启用 ParallelOldGC
  • 同时禁用 ParallelGC

打印出来的堆信息如下:

Heap
 PSYoungGen      total 9216K, used 3544K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 43% used [0x00000000ff600000,0x00000000ff976108,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 10239K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 99% used [0x00000000fec00000,0x00000000ff5ffc10,0x00000000ff600000)
 Metaspace       used 3312K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 359K, capacity 388K, committed 512K, reserved 1048576K

可以看出,堆信息几乎一样。所以单纯从堆信息看,是无法区分到底是使用了哪种GC收集器。

使用表格比较下两者的区别:

参数 新生代-垃圾收集器 老年代-垃圾收集器 Old-线程 Old-算法
UseParallelOldGC Parallel Scavenge Parallel Old 多线程 标记整理
UseParallelGC Parallel Scavenge PS MarkSweep(即Serial Old 单线程 标记整理

吞吐量优先 的场景下,建议使用第一个垃圾收集器组合。

 
结论:既然jdk源码都说默认启用了 ParallelOldGC ,那就记住这个结论吧。

 


四、深挖 demo 代码

现在回过头聊一聊demo中的代码意图。
代码:

byte[] alloc = new byte[10239*1024];

是创建了一个大的数组。其中的数组元素的个数是:10239k
然后我们看到其全部放到了老年代中:

ParOldGen       total 10240K, used 10239K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 99% used [0x00000000fec00000,0x00000000ff5ffc10,0x00000000ff600000)

这些数组元素占用的空间:10239k,所以可以看出每个数组元素占了1个byte(字节)。
 
那么,这里的对象为什么会直接进入了老年代了呢

首先,这里的byte数组是一个大对象,并且它需要一段连续的空间(因为是数组)。
其次,这个大对象的占用空间有点特殊,是 10239k,恰好比 10M1k

那么,新生代可以容纳得下它吗?
前面我们说过新生代的总大小是10M,但是因为这10M 包含了1个 8M 的Eden区,还有2个 1M 的 Survivor区,所以实际可用空间是 9M(即 9216K)。很显然 9216K 是小于 10239k 的。

那么接下来需要再检查下老年代空间是否足够。老年代的空间是 10M ,前面讲过这个值比 10239k 正好大 1k 。所以,老年代是能容纳下这些数据的,根据 内存的分配策略,大对象会直接进入老年代。

因为jdk1.8使用的是 Parallel Scavenge 收集器,所以下面的虚拟机参数是不生效的:

# 单位是byte,1048576=1024k=1M
-XX:PretenureSizeThreshold=1048576

 


五、总结

本文介绍了如何查看JVM使用的垃圾收集器。在网友的启示下,获知jdk1.8.0中默认使用的是 Parallel Scavenge + Parallel Old 收集器。通过修改虚拟机参数还得知:Parallel Scavenge+PS MarkSweepParallel Scavenge + Parallel Old 两种收集器的GC日志是一样的。

然后又通过代码验证了一下大对象的内存分配策略:了解了什么情况下大对象会直接进入老年代。

 


参考:

  • 《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》

  • https://github.com/zlserver/jvm_code

  • https://gitee.com/tanglongan/understanding-the-jvm

  • https://github.com/TangBean/understanding-the-jvm/tree/master

 
 
 
 
 

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-29 04:56:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-29 04:56:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-29 04:56:04       82 阅读
  4. Python语言-面向对象

    2024-03-29 04:56:04       91 阅读

热门阅读

  1. TCS3393 DATA MINING

    2024-03-29 04:56:04       29 阅读
  2. Axios和Ajax俩者的区别

    2024-03-29 04:56:04       36 阅读
  3. 【WPF应用13】WPF基本控件-DockPanel布局详解与示例

    2024-03-29 04:56:04       46 阅读
  4. 11 开源鸿蒙OpenHarmony轻量系统源码分析

    2024-03-29 04:56:04       39 阅读
  5. spark dataFrame数据写出

    2024-03-29 04:56:04       41 阅读
  6. Pytorch的named_children, named_modules和named_children

    2024-03-29 04:56:04       29 阅读
  7. 【C++ RapidJson】使用RapidJson写入文件

    2024-03-29 04:56:04       38 阅读