意外的内存分配:JIT编译抖动,分配jit编译抖动
分享于 点击 37847 次 点评:187
意外的内存分配:JIT编译抖动,分配jit编译抖动
我在 ByteWatcher (见我最后一篇文章)工作时,碰到了一些奇怪的事情。
这是一段用来查找在特殊线程上分配了多少内存的真实代码片段。
return (long) mBeanServer.invoke( name, GET_THREAD_ALLOCATED_BYTES, PARAMS, SIGNATURE );
全部上下文参见这里。
(ByteWatcher的工作方式是周期性地调用这个方法来监视内存分配。)
要注意的一个重点是,特别是当程序希望获得精确大小的内存分配时来调用上面的代码——会引起内存分配。
该调用引起的内存分配必须从返回数字中扣除,因此我们隔离了程序引起的内存分配,例如调用meanBeanServer = 程序线程的内存分配 + 调用开销 。
我注意到,这个内存分配总是336字节。然而,当我在一个循环中调用这个方法时,发现了一些有趣的东西。几乎每隔一段时间,就会分配不同数量的内存。
测试如下:
<a href="http://www.jobbole.com/members/madao">@Test</a> public void testQuietMeasuringThreadAllocatedBytes() { ByteWatcherSingleThread am = new ByteWatcherSingleThread(); System.out.println("MeasuringCostInBytes = " + am.getMeasuringCostInBytes()); long[] marks = new long[1_000_000]; for (int i = 0; i < 1_000_000; i++) { marks[i] = am.threadAllocatedBytes(); } long prevDiff = -1; for (int i = 1; i < 1_000_000; i++) { long diff = marks[i] - marks[i - 1]; if (prevDiff != diff) System.out.println("Allocation changed at iteration " + i + "->" + diff); prevDiff = diff; } }
典型的结果:
MeasuringCostInBytes = 336 Allocation changed at iteration 1->336 Allocation changed at iteration 12->28184 Allocation changed at iteration 13->360 Allocation changed at iteration 14->336 Allocation changed at iteration 1686->600 Allocation changed at iteration 1687->336 Allocation changed at iteration 2765->672 Allocation changed at iteration 2766->336 Allocation changed at iteration 5458->496 Allocation changed at iteration 5459->336 Allocation changed at iteration 6213->656 Allocation changed at iteration 6214->336 Allocation changed at iteration 6535->432 Allocation changed at iteration 6536->336 Allocation changed at iteration 6557->8536 Allocation changed at iteration 6558->336 Allocation changed at iteration 7628->576 Allocation changed at iteration 7629->336 Allocation changed at iteration 8656->4432 Allocation changed at iteration 8657->336 Allocation changed at iteration 9698->968 Allocation changed at iteration 9699->336 Allocation changed at iteration 11881->1592 Allocation changed at iteration 11882->336 Allocation changed at iteration 12796->1552 Allocation changed at iteration 12797->336 Allocation changed at iteration 13382->456 Allocation changed at iteration 13383->336 Allocation changed at iteration 14844->608 Allocation changed at iteration 14845->336 Allocation changed at iteration 36685->304 Allocation changed at iteration 52522->336 Allocation changed at iteration 101440->400 Allocation changed at iteration 101441->336
鉴于程序中绝对没有内存分配,这个谜题让我很困惑,为什么相同的调用有时会分配不同大小的内存。
总结下来,超过100万次运行,程序分配不同大小内存大约25次。值得注意的是,在10万次迭代后,再没有尖峰出现了。
我与Heinz Kabutz和Chris Newland分享了这个问题。Chris注意到,由于JIT编译抖动内存分配下降了。通过设置-Xint标示(仅在解释模式下运行,没有JIT编译)重新运行程序,就可以清楚地得到结论。现在只有2个尖峰了。
MeasuringCostInBytes = 336 Allocation changed at iteration 1->336 Allocation changed at iteration 12->28184 Allocation changed at iteration 13->360 Allocation changed at iteration 14->336
类似的,配置-Xcomp标示(仅编译模式)运行:
MeasuringCostInBytes = 336 Allocation changed at iteration 1->336 Allocation changed at iteration 12->29696 Allocation changed at iteration 13->360 Allocation changed at iteration 14->336
所以,现在我们可以非常有信心的说,JIT编译抖动引起了这些奇怪的内存分配。
我完全不明白这是为什么,但我想这是可以理解的。为了弥补这一点,我在ByteWatcher的构造器中引入了校正阶段,该阶段后来被Heinz优化了。
你可以在这里看到修正的代码,它包含几个阶段:
- 调用该方法计算出紧密循环(我们调用它10万次)中需要分配多少线程——让JIT提前编译好这些代码。
- 等待50毫秒——允许JVM有机会完成它的编译抖动。
在构造器中有了这些代码,即使不带任何选项运行都不会有内存分配尖峰了。
结论
- JIT编译抖动造成了一些内存分配。
- 运行不带编译抖动(例如仅解释模式或编译模式)的程序极大的减少了内存分配但没有完全消除。
- 10万次运行后,内存分配停止。这表明了抖动需要10万次运行才会停止。这很有趣,因为我们知道代码需要1万次迭代后进行编译。
译文链接: http://www.wld5.com/16833.html
[ 转载请保留原文出处、译者和译文链接。]
用户点评