欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM堆大小的自适应能力 jvm 

程序员文章站 2022-04-16 20:22:46
...
在完善我们的测试台以便提高Plumbr](https://plumbr.eu/gc)排查GC故障能力的时候,我编写了[一个小小的测试用例,我觉得应该会有不少人对它感兴趣。我的目标是测试JVM在不同的伊甸区(Eden), 存活区(Survivor)以及年老代空间的分配情况下的自适应能力。


这个测试用例就是在批量地生成对象。每秒会批量生成一批,每批大概是500KB的大小。这些对象的生命周期是5秒钟,之后它们的引用会被删除掉,然后就可以进行垃圾回收了。

本次测试是运行在Mac OS X的Oracle Hotspot 7 JVM上的,使用的是ParallelGC策略,堆的大小是30M。知道了运行的平台之后,我们可以断定出JVM会按下面的堆配置进行启动:
  • 年轻代大小10M,年老代20M,由于没有显式地指定堆的分配比例,JVM默认会按1:2的比例来划分年轻代和年老代的堆空间。
  • 在我的Mac OS X上,10MB的年轻代又会进一步划分为伊甸区和两个存活区,分别是8MB和2*1MB。再强调一遍,这些默认值都是和特定平台相关的。

  • 启动测试用例并通过jstat查看了GC的详细情况后,事实证明我们刚才的预测是正确的:


    
    My Precious:gc-pressure me$ jstat -gc 2533 1s
     S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
    1024.0 1024.0  0.0    0.0    8192.0   5154.4   20480.0      0.0     21504.0 2718.9      0    0.000   0      0.000    0.000
    1024.0 1024.0  0.0    0.0    8192.0   5502.1   20480.0      0.0     21504.0 2720.1      0    0.000   0      0.000    0.000
    1024.0 1024.0  0.0    0.0    8192.0   6197.5   20480.0      0.0     21504.0 2721.0      0    0.000   0      0.000    0.000
    1024.0 1024.0  0.0    0.0    8192.0   6545.2   20480.0      0.0     21504.0 2721.2      0    0.000   0      0.000    0.000
    1024.0 1024.0  0.0    0.0    8192.0   7066.8   20480.0      0.0     21504.0 2721.6      0    0.000   0      0.000    0.000
    1024.0 1024.0  0.0    0.0    8192.0   7588.3   20480.0      0.0     21504.0 2722.1      0    0.000   0      0.000    0.000
    
    
    
    



    现在我们还可以继续预测一下接下来会发生什么:
  • 伊甸区的8MB会在16秒内填充满——记住,我们每秒会生成500KB的对象。
  • 任何时刻都会有正好2.5MB的存活对象——每秒生成500KB并持有这些对象5秒钟,最后就是这个数
  • 伊甸区占满的时候触发年轻代GC(Minor GC)——也就是说每16秒会出现一次年轻代的GC
  • 年轻代GC后,会出现过早提升(Premature promotion,注:这些对象过早地提升到了年老代)——存活区只有1MB的大小,而存活对象有2.5MB,1MB的存活区无法容纳这些对象。因此唯一的途径就是将存活区存放不下的1.5MB(2.5MB-1MB)的对象移动到年老代里。


  • 查看下日志可以证实我的这个猜想:

    
    My Precious:gc-pressure me$ jstat -gc -t 2575 1s
    Time   S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
      6.6 1024.0 1024.0  0.0    0.0    8192.0   4117.9   20480.0      0.0     21504.0 2718.4      0    0.000   0      0.000    0.000
      7.6 1024.0 1024.0  0.0    0.0    8192.0   4639.4   20480.0      0.0     21504.0 2718.7      0    0.000   0      0.000    0.000
            ... cut for brevity ...
     14.7 1024.0 1024.0  0.0    0.0    8192.0   8192.0   20480.0      0.0     21504.0 2723.6      0    0.000   0      0.000    0.000
     15.6 1024.0 1024.0  0.0   1008.0  8192.0   963.4    20480.0     1858.7   21504.0 2726.5      1    0.003   0      0.000    0.003
     16.7 1024.0 1024.0  0.0   1008.0  8192.0   1475.6   20480.0     1858.7   21504.0 2728.4      1    0.003   0      0.000    0.003
            ... cut for brevity ...
     29.7 1024.0 1024.0  0.0   1008.0  8192.0   8163.4   20480.0     1858.7   21504.0 2732.3      1    0.003   0      0.000    0.003
     30.7 1024.0 1024.0 1008.0  0.0    8192.0   343.3    20480.0     3541.3   21504.0 2733.0      2    0.005   0      0.000    0.005
     31.8 1024.0 1024.0 1008.0  0.0    8192.0   952.1    20480.0     3541.3   21504.0 2733.0      2    0.005   0      0.000    0.005
            ... cut for brevity ...
     45.8 1024.0 1024.0 1008.0  0.0    8192.0   8013.5   20480.0     3541.3   21504.0 2745.5      2    0.005   0      0.000    0.005
     46.8 1024.0 1024.0  0.0   1024.0  8192.0   413.4    20480.0     5201.9   21504.0 2745.5      3    0.008   0      0.000    0.008
     47.8 1024.0 1024.0  0.0   1024.0  8192.0   961.3    20480.0     5201.9   21504.0 2745.5      3    0.008   0     
     
     
    



    触发垃圾回收触发的时间是15秒左右,而不是16秒,它会清理掉伊甸区并将大约1MB的对象移动到存活区,同时将剩余的那些对象移动到年老代。

    目前为止一切都和我们猜测的一样。JVM的确是按我们所理解的方式来运行的。有趣的是一旦JVM监控到GC的行为并知道发生的情况之后。在我们这个测试用例中,这个发生在90秒左右的时候:

    
    My Precious:gc-pressure me$ jstat -gc -t 2575 1s
    Time   S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
     94.0 1024.0 1024.0  0.0   1024.0  8192.0   8036.8   20480.0     8497.0   21504.0 2748.8      5    0.012   0      0.000    0.012
     95.0 1024.0 3072.0 1024.0  0.0    4096.0   353.3    20480.0    10149.6   21504.0 2748.8      6    0.014   0      0.000    0.014
     96.0 1024.0 3072.0 1024.0  0.0    4096.0   836.6    20480.0    10149.6   21504.0 2748.8      6    0.014   0      0.000    0.014
     97.0 1024.0 3072.0 1024.0  0.0    4096.0   1350.0   20480.0    10149.6   21504.0 2748.8      6    0.014   0      0.000    0.014
     98.0 1024.0 3072.0 1024.0  0.0    4096.0   1883.5   20480.0    10149.6   21504.0 2748.8      6    0.014   0      0.000    0.014
     99.0 1024.0 3072.0 1024.0  0.0    4096.0   2366.8   20480.0    10149.6   21504.0 2748.8      6    0.014   0      0.000    0.014
    100.0 1024.0 3072.0 1024.0  0.0    4096.0   2890.2   20480.0    10149.6   21504.0 2748.8      6    0.014   0      0.000    0.014
    101.0 1024.0 3072.0 1024.0  0.0    4096.0   3383.7   20480.0    10149.6   21504.0 2748.8      6    0.014   0      0.000    0.014
    102.0 1024.0 3072.0 1024.0  0.0    4096.0   3909.7   20480.0    10149.6   21504.0 2748.8      6    0.014   0      0.000    0.014
    103.0 3072.0 3072.0  0.0   2720.0  4096.0   323.0    20480.0    10269.6   21504.0 2748.9      7    0.016   0      0.000    0.016
    
    



    我们可以看到JVM的强大的适能力。一旦了解了应用的行为之后,JVM会将存活区调整为足够容纳下所有的存活对象。现在年轻代的配置变成了这样:
  • 伊甸区4MB
  • 存活区每个3MB


  • 调整之后GC的频率会有所提高——伊甸区现在只有原来的一半了,因此现在是每8秒一次GC而不是原来的16秒。不过好处也是显而易见的,现在的存活区已经足以存放所有的存活对象了。考虑到已经没有对象能活过一次年轻代GC的周期(不过还得记住,任何时刻都仍有2.5MB的存活对象),因此也不用再将对象提升到年老代中了。

    继续观察JVM后我们会发现年老代的使用率在调整后会一直保持不变。对象不会再移动到年老代中了,不过由于也不会再触发年老代GC,所以在JVM调整前提升进来的那10MB的垃圾对象就会一直存放在年老代里面了。

    你也可以把JVM的这个“神奇的自适应性”的功能给屏蔽掉,如果你确定要这么做的话。在JVM的参数中指定-XX-UseAdaptiveSizingPolicy就会让JVM严格遵循启动时所指定的参数,而不会违背你的旨意。请谨慎使用这一选项,现代的JVM通常都能自动地给你预测出恰当的配置。


    原创文章转载请注明出处:http://it.deepinmind.com

    英文原文链接
    相关标签: jvm