VisualVM - 奇怪的自我时间

时间:2016-09-24 17:00:01

标签: java profiling visualvm

今天我对我得到的Visual VM分析结果感到困惑。

我有以下简单的Java方法:

public class Encoder {
  ...
  private BitString encode(InputStream in, Map<Character, BitString> table) 
      throws IOException {

    BufferedReader reader = new BufferedReader(new InputStreamReader(in));

    BitString result = new BitString();
    int i;
    while ((i = reader.read()) != -1) {
      char ch = (char) i;
      BitString string = table.get(ch);
      result = result.append(string);
    }

    return result;
  }
}

此方法一次一个地从流中读取字符。对于每个字符,它查找它的位串表示,并且它连接这些位串以表示整个流。

BitString是一种自定义数据结构,它使用基础字节数组表示一系列位。

该方法表现非常糟糕。问题在于BitString#append - 该方法创建一个新的字节数组,从两个输入BitStrings复制这些位并将其作为新的BitString实例返回。

public BitString append(BitString other) {

  BitString result = new BitString(size + other.size);
  int pos = 0;

  for (byte b : this) {
    result.set(pos, b);
    pos++;
  }
  for (byte b : other) {
    result.set(pos, b);
    pos++;
  }

  return result;
}

但是,当我尝试使用VisualVM来验证发生了什么时,我得到的是:

profiling

我对Visual VM和概要分析的经验很少。根据我的理解,这似乎问题在encode方法本身而不是append中存在。

可以肯定的是,我用自定义时间测量包围了整个编码方法和附加调用,如下所示:

public class Encoder {
  private BitString encode(InputStream in, Map<Character, BitString> table)
      throws IOException {

>>  long startTime = System.currentTimeMillis();
>>  long appendDuration = 0;

    BufferedReader reader = new BufferedReader(new InputStreamReader(in));

    BitString result = new BitString();
    int i;
>>  long count = 0;
    while ((i = reader.read()) != -1) {
      char ch = (char) i;
      BitString string = table.get(ch);

>>    long appendStartTime = System.currentTimeMillis();
      result = result.append(string);
>>    long appendEndTime = System.currentTimeMillis();
>>    appendDuration += appendEndTime - appendStartTime;

>>    count++;
>>    if (count % 1000 == 0) {
>>      log.info(">>> CHARACTERS PROCESSED: " + count);
>>      long endTime = System.currentTimeMillis();
>>      log.info(">>> TOTAL ENCODE DURATION: " + (endTime - startTime) + " ms");
>>      log.info(">>> TOTAL APPEND DURATION: " + appendDuration + " ms");
>>    }
    }

    return result;
  }
}

我得到了以下结果:

CHARACTERS PROCESSED: 102000
TOTAL ENCODE DURATION: 188276 ms
APPEND CALL DURATION: 188179 ms

这似乎与Visual VM的结果相矛盾。

我错过了什么?

2 个答案:

答案 0 :(得分:1)

您正在看到此行为,因为VisualVM只能在安全点处对调用堆栈进行采样,并且JVM正在从代码中优化安全点。这导致样本在“自发时间”下集总在一起,从而使其人为夸大且产生误导。有两种可能的修复方法:

  • 要使VisualVM更好地工作,请添加JVM选项以保持更多安全点,例如-XX:-Inline-XX:+UseCountedLoopSafepoints。这些会使您的代码变慢一些,但会使分析结果更加准确。这个解决方案很简单,通常就足够了。只记得在不进行概要分析时删除这些选项!
  • 如果您不介意切换分析器,则可以使用Java Mission Controlhonest-profiler。它们具有使用JVM的特殊API在安全点以外的地方进行采样的能力。由于您要对完全优化的代码进行性能分析,因此该解决方案更加准确,但是在我看来,这些分析器比VisualVM更难使用。

在您的特定情况下,JVM可能内联了对BitSting.append()的方法调用以提高性能。这将导致通常在方法调用结束时删除的安全点,这意味着该方法将不再显示在事件探查器中。

有一篇很棒的博客文章here,其中有关于安全点是什么以及它们如何工作的更多详细信息,而另一本here则更详细地讨论了安全点和采样分析器之间的交互作用。

答案 1 :(得分:0)

[这个答案是无效的。但我保留它直到OP得到一个成员的帮助,因为这篇文章包含两个评论,帮助其他人理解问题]。

在这种情况下,VisualVM测量了实际的CPU时间,但您测量的时间值是&#34;经过的时间&#34;。

如果执行线程必须等待IO或网络,则该时间不会被测量为CPU时间。