log4j2异步日志通过lmax disruptor实现零阻塞日志写入:启用asyncloggercontextselector、避免字符串拼接与同步计算、选用无锁appender、监控ringbuffer并合理调优。

高并发场景下,Java 日志性能瓶颈往往不在业务逻辑,而在日志写入本身——同步落盘会阻塞主线程,拖慢响应、压垮吞吐。真正有效的解法不是“少打日志”,而是让日志彻底不卡主线程。Log4j2 的异步日志(基于 LMAX Disruptor)正是为此设计:它把日志记录变成一次毫秒级的指针移动,后续格式化、IO 全部交给后台线程。
启用全局异步日志(最简单也最关键的一步)
无需修改代码,只需两件事:
- 确保 classpath 中有 log4j-core(≥ 2.17)和 disruptor(≥ 3.4.4;Log4j2 2.17+ 已内置)
- JVM 启动时加参数:-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
启动后留意日志中是否出现 Using AsyncLogger: true,这是生效标志。配置文件中无需改 <logger></logger> 或 <root></root>,所有 Logger 自动走异步路径。
避开写法陷阱(否则异步也白搭)
即使开了异步,错误调用仍会让主线程变慢:
-
禁用字符串拼接:❌
logger.info("User " + user.getId() + " login")→ ✅logger.info("User {} login", user.getId()) -
延迟执行耗时逻辑:❌
logger.debug("Result: " + heavyCalc())→ ✅logger.debug("Result: {}", () -> heavyCalc()) -
异常直接传对象:✅
logger.error("Failed", ex),别手动ex.printStackTrace()
优化 Appender 避免 I/O 成新瓶颈
异步只是把日志“发出去”,落盘仍可能拖慢消费者线程:
- 控制台输出:用
<console direct="true"></console>,绕过System.out的同步锁 - 文件输出:优先选
RollingRandomAccessFileAppender,支持内存映射(mmap)和无锁写入 - 关闭强制刷盘:
immediateFlush="false"(默认已是 false),配合bufferSize="8192"提升批量效率 - 生产环境日志级别设为 INFO 或更高,禁用 DEBUG,防止海量低价值日志挤占 RingBuffer
监控与调优 RingBuffer 状态
异步日志表现高度依赖缓冲区健康度:
- 加 JVM 参数:-Dlog4j2.status=INFO,关注日志中是否频繁出现 RingBuffer was full
- 若满载频繁,增大缓冲区:-Dlog4j2.asyncLoggerRingBufferSize=262144(256K)
- 禁用
includeLocation="true"(如需栈信息,仅在调试时临时开启),否则每次都要遍历栈帧,性能暴跌 - 应用关闭前务必显式调用
LogManager.shutdown(),确保 RingBuffer 中日志被消费完
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











