【发布时间】:2021-03-18 16:46:34
【问题描述】:
在某些情况下,在以下代码中调用 java.util.logging.Logger.log() 时,我发现延迟非常高:
private static Object[] NETWORK_LOG_TOKEN = new Object[] {Integer.valueOf(1)};
private final TimeProbe probe_ = new TimeProbe();
public void onTextMessagesReceived(ArrayList<String> msgs_list) {
final long start_ts = probe_.addTs(); // probe A
// Loop through the messages
for (String msg: msgs_list) {
probe_.addTs(); // probe B
log_.log(Level.INFO, "<-- " + msg, NETWORK_LOG_TOKEN);
probe_.addTs(); // probe C
// Do some work on the message ...
probe_.addTs(); // probe D
}
final long end_ts = probe_.addTs(); // probe E
if (end_ts - start_ts >= 50) {
// If the run was slow (>= 50 millis) we print all the recorded timestamps
log_.info(probe_.print("Slow run with " + msgs_list.size() + " msgs: "));
}
probe_.clear();
}
probe_ 只是这个非常基本的类的一个实例:
public class TimeProbe {
final ArrayList<Long> timestamps_ = new ArrayList<>();
final StringBuilder builder_ = new StringBuilder();
public void addTs() {
final long ts = System.currentTimeMillis();
timestamps_.add(ts);
return ts;
}
public String print(String prefix) {
builder_.setLength(0);
builder_.append(prefix);
for (long ts: timestamps_) {
builder_.append(ts);
builder_.append(", ");
}
builder_.append("in millis");
return builder_.toString();
}
public void clear() {
timestamps_.clear();
}
}
这里是记录 NETWORK_LOG_TOKEN 条目的处理程序:
final FileHandler network_logger = new FileHandler("/home/users/dummy.logs", true);
network_logger2.setFilter(record -> {
final Object[] params = record.getParameters();
// This filter returns true if the params suggest that the record is a network log
// We use Integer.valueOf(1) as our "network token"
return (params != null && params.length > 0 && params[0] == Integer.valueOf(1));
});
在某些情况下,我得到以下输出(添加带有探针 A、B、C、D、E 的标签以使事情更清楚):
// A B C D B C D E
slow run with 2 msgs: 1616069594883, 1616069594883, 1616069594956, 1616069594957, 1616069594957, 1616069594957, 1616069594957, 1616069594957
除了 B 和 C 之间的代码行(在 for 循环的第一次迭代期间),一切都花费不到 1 毫秒,这需要 73 毫秒。并非每次调用onTextMessagesReceived() 时都会发生这种情况,但它确实是个大问题。我欢迎任何解释这种缺乏可预测性的原因的想法。
附带说明一下,我检查了我的磁盘 IO 超低,并且这段时间没有发生 GC 暂停。我认为我的 NETWORK_LOG_TOKEN 设置在设计方面充其量是非常脆弱的,但我仍然想不出为什么有时,这第一条日志行需要永远。任何关于可能发生的事情的指示或建议将不胜感激:)!
【问题讨论】:
-
这是唯一需要这么长时间的日志语句吗?这对于您的配置中的日志记录语句是否不寻常?你怎么知道这是日志记录而不是你将 ts 添加到 arraylist 的方式?运行这 10k 次,看看它是否稳定下来怎么样?当它意识到您即将循环时,它可能与循环开始时启动的 jit 或其他优化有关,这种效果将在数千次运行中消除。通常,您不能运行一次代码并期望配置文件是合理或准确的。
-
您是否查看过系统上
System.currentTimeMillis()的粒度?如果这种情况很少见,可能是 NTP 同步,请尝试改用nanoTime()。它可能是一百万件事,尝试使用github.com/giltene/jHiccup 并查看它的输出。如果什么都没有显示,那么记录器当然有可能实际上在 IO 上被阻塞(将其缓冲区写出),请使用分析器进行确认。
标签: java performance logging