log4j2是高并发场景下的日志王者,因其基于lmax disruptor的异步日志机制,吞吐量可达百万级/秒,而logback依赖阻塞队列,性能相对较低。

Logback 或 Log4j2 这类日志组件在高并发场景下,容易因频繁创建日志事件、格式化字符串、异常堆栈等临时对象,加剧 GC 压力。优化核心不是“少打日志”,而是让日志框架本身更轻量、复用更多、延迟更少。
用占位符替代字符串拼接
避免在日志语句中直接用 + 拼接参数,尤其在高频路径(如请求入口、循环体)中:
- ❌ 错误写法:logger.info("User " + userId + " accessed " + url + " at " + System.currentTimeMillis());
- ✅ 正确写法:logger.info("User {} accessed {} at {}", userId, url, System.currentTimeMillis());
SLF4J 的占位符机制会在日志级别未启用时跳过参数计算和字符串构造;即使启用,也只在真正需要格式化时才执行,不提前生成中间 String 对象。
启用异步 Appender 并调大队列容量
同步写日志会让业务线程卡在 I/O 或编码上,同时触发大量短生命周期对象(如 LoggingEvent、StringBuilder、byte[] 缓冲区)。AsyncAppender 把日志事件转为轻量 Event 对象入队,由后台线程批量处理:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- Logback 中配置
<queuesize>8192</queuesize>(默认仅 256),避免队列满导致丢弃或阻塞 - 配合
<discardingthreshold>0</discardingthreshold>确保低优先级日志可被丢弃,而非拖慢主线程 - 注意:AsyncAppender 本身不创建日志内容对象,但需确保其包裹的底层 Appender(如 RollingFileAppender)不开启
immediateFlush="true"
关闭不必要的日志上下文与 MDC 复制
MDC(Mapped Diagnostic Context)在每次日志调用时会做 ThreadLocal Map 的深拷贝(尤其在异步场景),产生额外对象:
- 若未使用 MDC,直接移除所有
MDC.put()和相关过滤器 - 若必须使用,避免在每条日志前重复 put/remove;改用一次初始化 + 异步线程继承策略(Logback 1.4+ 支持
includeCallerData="false"和resetMDC="true"控制) - 禁用
callerStackDepth(默认 5 层),避免每次记录都抓取 StackTraceElement 数组
精简 PatternLayout 与禁用运行时反射
Logback 的 %caller、%ex、%X{key} 等转换器在每次日志输出时都会触发对象创建或反射调用:
- 生产环境 pattern 中移除
%caller、%line、%class等开销大的字段 - 异常堆栈(
%ex)只在 ERROR 级别保留,INFO/WARN 级别用%throwable{short}或直接省略 - 避免在 pattern 中使用
%mdc{xxx}读取不存在的 key,会触发 HashMap.get() + null 判断链
不复杂但容易忽略——多数 GC 峰值不是来自业务代码,而是日志框架在“默默干活”时悄悄分配的临时对象。把日志当成一条数据流水线来看,每一处格式化、复制、序列化,都是对象诞生的源头。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










