转载

意外的内存分配: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优化了。

你可以在 这里 看到修正的代码,它包含几个阶段:

  1. 调用该方法计算出紧密循环(我们调用它10万次)中需要分配多少线程——让JIT提前编译好这些代码。
  2. 等待50毫秒——允许JVM有机会完成它的编译抖动。

在构造器中有了这些代码,即使不带任何选项运行都不会有内存分配尖峰了。

结论

  • JIT编译抖动造成了一些内存分配。
  • 运行不带编译抖动(例如仅解释模式或编译模式)的程序极大的减少了内存分配但没有完全消除。
  • 10万次运行后,内存分配停止。这表明了抖动需要10万次运行才会停止。这很有趣,因为我们知道代码需要1万次迭代后进行编译。

原文链接: javacodegeeks 翻译:ImportNew.com -liken

译文链接:[]
正文到此结束
Loading...