AsyncLoggerConfig 导致线程 Block

通过监控平台查看线程监控指标,从 Blocked 线程堆栈不难看出是和日志打印相关。分析异常线程堆栈 与(AsyncAppender 导致线程 Block)业务异常一样。

解析异常堆栈

Log4j2 关于日志的几个重要概念:

● ,日志配置标签,用于 XML 日志配置文件中,对应 Log4j2 框架中的 LoggerConfig 类,同步分发日志事件到对应 Appender。

● ,日志配置标签,用于 XML 日志配置文件中,对应 Log4j2框架中的 AsyncLoggerConfig 类,内部使用 Disruptor 队列异步分发日志事件到对应 Appender。

● Logger,同步日志类,用于创建同步日志实例,同步调用 ReliabilityStrategy处理日志。

● AsyncLogger**,异步日志类,用于创建异步日志实例**,内部使用 Disruptor 队列实现异步调用 ReliabilityStrategy 处理日志

由于未配置 Log4jContextSelector 参数,所以使用的是同步 Logger,即通过 LoggerFactory.getLogger 方法获取的是 Logger 类实例而不是 AsyncLogger类实例,同时由于项目的 log4j2.xml 配置文件里配置了 标签,所以其底层是 Logger 和 AsyncLoggerConfig 组合

AsyncLoggerConfig 处理日志事件,其内部使用 Disruptor队列,在生成队列元素时,由 translator 来负责填充元素字段,并把填充后的元素放入 RingBuffer 中,于此同时,独立的异步线程从 RingBuffer 中消费事件,并调用配置在该 AsyncLoggerConfig 上的 Appender 处理日志请求。

AsyncLoggerConfig 提 供 了 带 有 Disruptor 队 列 实 现 的 代 理 类 即 AsyncLoggerConfigDisruptor, 在 日 志 事 件 进 入 RingBuffer 时, 由 于 项 目 使 用 的 是ReusableLogEventFactory,所以由 MUTABLE_TRANSLATOR 负责初始化日志事件,在此过程中会调用 getThrownProxy 方法创建 ThrowableProxy 实例,进而在 ThrowableProxy 构造函数内部触发解析、加载异常堆栈类

问题小结

Log4j2 打印异常日志时AsyncLoggerConfig 会初始化 Disruptor RingBuffer 日志元素字段,并进一步触发解析、加载异常堆栈类JVM 通过生成字节码的方式优化反射调用性能,但该动态生成的类无法被 WebAppClassLoader 类加载器加载,因此当大量包含反射调用的异常堆栈被输出到日志时会频繁地触发类加载,由于类加载过程是 synchronized 同步加锁的,且每次加载都需要读取文件,速度较慢,从而导致线程 Block。