意外的内存分配: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 &lt; 1_000_000; i++) {
      marks[i] = am.threadAllocatedBytes();
    }

    long prevDiff = -1;
    for (int i = 1; i &lt; 1_000_000; i++) {
      long diff = marks[i] - marks[i - 1];
      if (prevDiff != diff)
        System.out.println("Allocation changed at iteration " + i + "-&gt;" + 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
译文链接: http://www.importnew.com/16833.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: liken

(新浪微博:@lihenair

查看liken的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部