无垃圾稳态日志

垃圾收集暂停是导致延迟峰值的常见原因,对于许多系统来说,需要花费大量精力来控制这些暂停。

许多日志库,包括以前版本的Log4j,在稳态日志记录期间分配临时对象,如日志事件对象、字符串、char数组、字节数组等。这增加了垃圾收集器的压力,并增加了GC暂停发生的频率。

从2.6版本开始,Log4j在默认情况下以“无垃圾”模式运行,其中对象和缓冲区被重用,并且尽可能不分配临时对象

还有一种“低垃圾”模式,它不是完全没有垃圾,但不使用ThreadLocal字段。当Log4j检测到它在web应用程序中运行时,这是默认模式。

最后,可以关闭所有无垃圾逻辑并以“经典模式”运行。详细信息请参见下面的“配置”部分。

一个例子

为了突出无垃圾日志可以带来的差异,我们使用Java Flight Recorder来测量一个简单的应用程序,该应用程序除了尽可能频繁地记录一个简单的字符串外,什么都不做,持续大约12秒。

应用程序被配置为使用异步日志记录器、随机访问文件附加程序和 %d %p %c{1.} [%t] %m %ex%n 模式布局。(异步记录器使用Yield WaitStrategy。)

任务控制显示,使用Log4j 2.5,该应用程序以大约809 MB/秒的速率分配内存,产生141个小收集。

Log4j 2.6在此配置中不分配临时对象,因此使用Log4j 2.6的相同应用程序的内存分配率为1.6 MB/秒,并且没有gc,没有垃圾收集。

配置

Log4j 2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现。

保存非jdk类的ThreadLocal字段在web应用程序取消部署后,当应用程序服务器的线程池继续引用这些字段时,可能会导致web应用程序中的内存泄漏。

为了避免造成内存泄漏,当Log4j检测到它在web应用程序中被使用时(当javax.servlet.Servlet类在类路径中,或者当系统属性log4j2在类路径中),它将不会使用这些 ThreadLocals.isWebapp 设置为“true”)。

一些减少垃圾的功能不依赖于ThreadLocals,并且默认情况下对所有应用程序都是启用的:在Log4j 2.6中,将日志事件转换为文本和文本转换为字节可以通过直接将文本编码为可重用的ByteBuffer来完成,而无需创建中间的字符串,char数组和字节数组。因此,虽然日志记录对于web应用程序来说还不是完全没有垃圾,但是垃圾收集器的压力仍然可以显著降低。

参考资料

https://logging.apache.org/log4j/2.x/manual/garbagefree.html