Log4j2 Garbage-free 无垃圾回收模式实践与总结

Log4j2 内置 Garbage-free(无垃圾)模式,可重用对象和缓冲区,减少日志记录时产生的垃圾对象,避免 JVM 进行 GC 回收,进而提升应用程序的性能与响应速度。以下以 Log4j2 的 2.24.3 版本为基础,客观、真实、全面地介绍 Garbage-free 无垃圾回收模式,并总结其特性和应用场景。

先说结论: Log4j2 的 Garbage-free 无垃圾回收模式在实际使用中支持和适用场景极为有限,意义不大,不建议开启。

一、如何启用

默认情况下,Log4j2 根据应用是否为 Web 类型(通过判断 classpath 中是否包含 Servlet 类)来决定是否启用 Garbage-free 运行模式。若是 Web 应用,不开启,此时 log4j2.isWebapp 和 log4j2.enableThreadlocals 属性值分别为 true 和 false;反之,可将两属性值设置为 false 和 true,则强制开启 Garbage-free 模式。

在 Web 应用中,若 ThreadLocal 变量持有非 JDK 类,且 Web 应用程序卸载后,应用服务器的线程池仍继续引用这些变量,可能导致应用内存泄漏。因此,为避免内存泄漏,Web 应用默认不开启 Garbage-free 模式。Web 应用可通过强制指定 log4j2.enableThreadlocals=true 来开启该模式。此外,若使用线程上下文字段(如 MDC),还需将 log4j2.garbagefreeThreadContextMap 属性设为 true。

建议将上述配置属性统一放在类路径(通常是 src/main/resources/)下的 log4j2.component.properties 文件中。这种配置方式可被 JVM 变量、系统环境变量和编码覆盖。

二、配套要求

启用 Garbage-free 模式(log4j2.enableThreadlocals 属性值设置为 true)后,需配套使用支持 Garbage-free 的 Layouts、Appenders 和 Filters,并合理使用 API,才能确保该模式真正生效。

1. 支持的 Layout

(1)有条件支持 GelfLayout、JsonTemplateLayout 和 PatternLayout 这三种 Layout,不支持常用的 CsvLayout。

(2)上述三种 Layout 并非完全支持。如果其中使用了不支持 Garbage-free 的参数、配置或方法,会导致该 Layout 不支持 Garbage-free。例如,在 PatternLayout 中输出 Exception、Method、Line、Location、Class、File 等字段,就会使得该 Layout 不支持 Garbage-free。

2. 支持的 Appender

(1)ConsoleAppender 及文件类 Appender,如 FileAppender、MemoryMappedFileAppender、RandomAccessFileAppender、RollingFileAppender(非轮转期间)、RollingRandomAccessFileAppender(非轮转期间)均支持。

(2)大部分涉及外部 IO(网络、数据库和消息队列等)的 Appender 均不支持,例如 JDBCAppender、KafkaAppender 等。

(3)上述结论仅源于官方文档,未经详细测试。总结 Layout 使用经验,建议仔细参考各 Appender 文档,判断是否存在具体限制。

3. 支持的 Filter

(1)CompositeFilter、DynamicThresholdFilter、LevelRangeFilter、MapFilter、MarkerFilter、StructuredDataFilter、ThreadContextMapFilter、ThresholdFilter 和 TimeFilter 均支持。

(2)上述结论仅源于官方文档,未经详细测试。总结 Layout 使用经验,建议仔细参考各 Appender 文档,判断是否存在具体限制。

4. 其他情况

(1)如果使用了 NDC(Nested Diagnostic Context),则无法支持 Garbage 模式。

(2)异步日志(AsyncLogger)如果使用默认的 timeout 等待策略,则支持 Garbage 模式。

(3)从测试情况来看,即便使用的 Layout、Appender 和 Filter 都支持 Garbage 模式,但如果 logger 输出内容包不支持 Garbage 模式的字段,最终也无法使用 Garbage 模式。

三、强烈建议

在实际项目中,即便应用启用 Garbage 模式,受各种使用限制和配置要求影响,实际也未必真正启用。强烈建议使用 JMH 对相应的日志配置进行压测,并添加"prof gc"参数,以验证 GC 的回收情况,从而判断该日志配置是否真正支持 Garbage 模式。

四、性能对比

以下我们将使用相同的性能测试基准,通过配置参数强制开启或关闭 Garbage 模式,以此判断该模式对性能的帮助。

1. 测试基准

(1)硬件:Windows 笔记本,配置为 I5-1350P CPU、32G DDR5 5200 内存以及三星 MZVL4512HBLU-00BLL 512G SSD(顺序写入速度为 2430MB/s)。

(2)软件:基于 JDK 1.8.171,使用 1.37 版 JMH 和 2.24.3 版 Log4j2。

(3)配置:采用 FileAppender 及其默认配置(append 和 immediateFlush 均为 true),使用同步 Logger 进行压测。

(4)参考日常使用情况,输出长度为 100 个的固定字符串,日志 PatternLayout 布局为:"% d {yyyy-MM-dd HH:mm:ss.SSS} %-5level % pid % t - % msg % n"。

(5)在 classpath 下添加 log4j2.component.properties 配置文件,通过 log4j2.enableThreadlocals 配置项来开启或关闭 Garbage-free 模式。

2. Log4j2 配置文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="log4j2AppenderTest" status="error">
	<Properties>
		<Property name="log.pattern">
			%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %pid %t - %msg %n
		</Property>
	</Properties>

	<Appenders>
		<Console name="Console">
			<PatternLayout pattern="${log.pattern}"/>
		</Console>
		<File name="File"
			fileName="log/log4j2-file.log">
			<PatternLayout pattern="${log.pattern}"/>
		</File>
	</Appenders>

	<Loggers>
		<Root level="debug">
			<AppenderRef ref="Console" />
		</Root>
		<Logger name="FileLogger" level="debug" additivity="false">
			<AppenderRef ref="File" />
		</Logger>
</Configuration>

3. JMH 压测代码

java 复制代码
@State(Scope.Benchmark)
public class Log4J2FileAppenderBenchmark {

    static Logger fileLogger;

    int delFilesCount = 0;

    @Setup(Level.Trial)
    public void setUp() throws Exception {
        System.setProperty("log4j.configurationFile", "log4j2.xml");
        fileLogger = LogManager.getLogger("FileLogger");
    }

    @TearDown
    public void tearDown() {
        System.clearProperty("log4j.configurationFile");
    }

    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @Benchmark
    public void fileLogger() {
        fileLogger.debug(Const.MSG_HAVE_100_CHARS);
    }
}

4. JMH 参数

JMH 执行的参数为:-jvmArgs "-Xmx512m -Xms512m" -f 2 -t 4 -w 10 -wi 2 -r 30 -i 2 -to 300 -prof gc,即设置 JVM 参数为 -Xmx512m -Xms512m(堆内存最大和最小均为 512MB),使用 2 个 fork(-f 2),每个 fork 使用 4 个线程(-t 4),预热阶段每次运行 10 秒(-w 10),预热迭代 2 次(-wi 2),正式测试每次运行 30 秒(-r 30),正式测试迭代 2 次(-i 2),超时时间为 300 秒(-to 300),并启用 GC 性能分析(-prof gc)。

5. 测试结果

类型 平均吞吐量 内存分配速率(MB/sec) 垃圾回收次数
Garbage-free 模式 137.1 ops/ms 10 MB/sec 0
非 Garbage-free 模式 135.5 ops/ms 18.8 MB/sec 18

6. 测试总结

(1)由于不少 PatternLayout 参数不支持无垃圾回收模式,因此上述 PatternLayout 配置参数略微简单,在相同配置下,启用 Garbage-free 模式后,内存分配量明显下降,且无 GC 回收及耗时,但吞吐量并无明显提高。

(2)此外,经其他测试发现,越不支持无垃圾回收模式的参数,越占内存,如输出日志所在的类、方法、行数及完整异常堆栈信息等。

五、总结

在日常研发中,我们主要将 Log4j2 用于 Web 开发,默认不开启 Garbage-free 模式。若强制启用,一旦使用不当,可能导致内存泄露。即便强制启用,实际使用时,各类日志输出字段需包含产生日志的类、方法、行数及完整异常堆栈等基本字段,而这些字段又不支持 Garbage-free 模式,仍会产生垃圾回收,最终真正启用该模式。所以我认为,Log4j2 的 Garbage-free 无垃圾回收模式在实际使用中支持和适用场景极为有限,意义不大,不建议开启。

六、参考文档

(1)log4j2.x garbagefree