在调查性能测试结果时,我发现Java Flight Recorder" Hot Methods"中报告了以下堆栈跟踪:
Stack Trace Sample Count Percentage(%)
----------- ------------ ----------
java.util.Arrays.rangeCheck(int, int, int) 358 2.212
java.util.Arrays.fill(char[], int, int, char) 358 2.212
java.lang.AbstractStringBuilder.setLength(int) 358 2.212
java.lang.StringBuilder.setLength(int) 358 2.212
org.apache.logging.log4j.core.async.RingBufferLogEvent.getMessageTextForWriting()
201 1.242
org.apache.logging.log4j.core.async.RingBufferLogEvent.setMessage(Message)
201 1.242
从堆栈跟踪中,看起来设置StringBuilder的长度结果为Arrays.fill
被调用。但是,我不明白为什么会发生这种情况,因为StringBuilder的长度设置为零。
查看Log4j的RingBufferLogEvent.getMessageTextForWriting
方法的代码,很明显StringBuilder的长度永远不会设置为零以外的任何其他值:
// RingBufferLogEvent.java
private StringBuilder getMessageTextForWriting() {
if (messageText == null) {
messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE); // 128
}
messageText.setLength(0); // <-- this call. Note: new length is ALWAYS zero.
return messageText;
}
我不明白这会如何导致Arrays.fill
被调用。查看AbstractStringBuilder
的代码,只有当新长度大于之前使用的字符数时,才应调用Arrays.fill
。
// AbstractStringBuilder.java
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength);
if (count < newLength) { // <-- how can this condition be true if newLength is zero?
Arrays.fill(value, count, newLength, '\0');
}
count = newLength;
}
当count < newLength
始终为零时,true
怎么可能newLength
?
JVM版本:
适用于linux-amd64 JRE(1.8.0_144-b01)的Java HotSpot(TM)64位服务器VM(25.144-b01),构建于2017年7月21日21:57:33 by&#34; java_re&#34;与gcc 4.3.0 20080428(Red Hat 4.3.0-8)
(和Log4j 2.10.0)
答案 0 :(得分:9)
JFR存在问题。它异步收集堆栈跟踪,即不仅在many other profilers do等安全点。一方面,这使得采样更加真实,因为没有安全点偏差。另一方面,HotSpot JVM无法在任何随机时刻正确地遍历堆栈。
HotSpot私有API AsyncGetCallTrace
尝试从定时器信号中断应用程序的任意点恢复当前堆栈跟踪。但是,代码中的某些位置对于堆栈遍历并不安全。如果在其中一个地方调用AsyncGetCallTrace
,它可能会返回无效的堆栈跟踪或根本没有堆栈跟踪。
这是AsyncGetCallTrace
的已知问题。 JDK-8022893有问题的例子和分析。 AsyncGetCallTrace
(以及基于它的所有分析器)返回不准确的跟踪,其中某些帧被邻居方法错误地替换,这是一种常见的情况。