今天,我对获得的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验证正在发生的事情时,这是我得到的:

java - VisualVM-奇怪的自我时间-LMLPHP

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

可以肯定的是,我在整个编码方法和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的结果相矛盾。

我想念什么?

最佳答案

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


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


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

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

关于java - VisualVM-奇怪的自我时间,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39678828/

10-10 14:04