在完善我们的测试台以便提高
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
英文原文链接
分享到:
相关推荐
在JDK6和JDK7中,Hotspot堆的管理各有特点,它会根据服务器类型、应用需求动态调整堆内存的大小和垃圾回收算法。Hotspot还提供了JVM监视工具,如VisualVM,它可以帮助开发者实时监控JVM的内存、CPU使用情况和线程...
5. 编译器选项:-XX:+UseBiasedLocking开启偏向锁,-XX:+UseAdaptiveSizePolicy允许JVM自适应调整内存策略。 三、JVM调试技巧 1. 异常堆栈追踪:通过jstack命令,可以获取到程序运行时的线程堆栈信息,帮助定位...
通常,可以通过-Xms和-Xmx参数设置初始堆大小和最大堆大小,如`-Xms256m -Xmx1024m`。 2. 新生代与老年代:新生代主要存放新创建的对象,老年代存放存活时间较长的对象。新生代和老年代的比例可以通过-XX:NewRatio...
2. **最小堆大小(Minimum Heap Size)**:使用 `-Xms` 参数设定,BEA(现Oracle)建议这个值应等于最大堆大小,以确保平稳的内存分配。 3. **最大堆大小(Maximum Heap Size)**:通过 `-Xmx` 参数设置,这是Java...
例如,"-Xms"用于设置初始堆内存大小,"-Xmx"设定最大堆内存大小。这些参数的设置对于应用性能至关重要,尤其是对于资源有限的环境,最小内存自适应机制就显得尤为重要。 三、最小内存自适应机制 最小内存自适应...
1. 动态调整JVM参数:通过JVM的命令行参数或者工具(如JConsole、VisualVM等),开发者可以在运行时动态调整堆大小、线程池大小等,以适应系统负载的变化。 2. 分区内存模型:如G1垃圾收集器采用分区内存模型,可以...
JVM通过将Java源代码编译成字节码(.class文件),然后在不同平台上运行这些字节码,从而实现了跨平台的能力。 ### 请你描述一下Java的内存区域? Java虚拟机在执行Java程序时会将其管理的内存划分为几个不同的区域...
G1收集器是一款全功能垃圾回收器,它将整个堆内存划分为多个大小相等的独立区域(Region),并根据需要将某些Region用作新生代Eden、Survivor或老年代空间。G1收集器支持面向局部回收的设计思路,能够根据停顿时间...
例如,JVM可以根据不同的工作负载和硬件配置来自动选择合适的垃圾收集器和堆大小。这种自适应行为称为“人体工程学”,它可以帮助开发者减少调整垃圾收集器所需的大量工作。 当选择不同的垃圾收集器时,需要考虑...
例如,通过调整 `-Xms` 和 `-Xmx` 参数来控制堆内存的初始大小和最大大小;通过 `-XX:+UseG1GC` 指定使用 G1 垃圾回收器等。 3. **性能监控与分析**:利用 JVisualVM、VisualGC 等工具监测 JVM 的运行状态,分析 ...
2. **堆(Heap)**:堆是JVM中最大的一块内存区域,用于存储对象实例。堆被所有线程共享,并且分为新生代和老年代,用于不同生命周期的对象分配。 - **新生代(Young Generation)**:对象首先被分配到新生代,...
Java作为一种广泛使用的高级编程语言,其自动管理内存的能力极大地简化了开发人员的工作。这其中的核心技术之一便是**垃圾回收机制**(Garbage Collection,简称GC)。本文旨在深入解析Java垃圾回收机制中的几种核心...