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。