The current Layout API does not make it easy for implementors to avoid creating temporary objects. Especially these methods:
The byte array returned from toByteArray(LogEvent) cannot be re-used between log events, since the caller cannot know how many bytes a partially filled array contains.
In practice, all Layout implementations in Log4j 2 except SerializedLayout implement the StringLayout subinterface. This means that the toSerializable() method needs to return a new String object for every log event.
Contrived example of an application that does nothing but log a simple string as often as possible: 94% of GC-ed memory is in the byte and char arrays.
I am interested in reducing or even eliminating the allocation of temporary objects for text-based layouts. Many of these use (and re-use) a StringBuilder to build a text representation of the current log event. Once this text representation is built, it needs to be converted to bytes that the Appender can consume. I am aware of two ways in the JDK to convert text to bytes:
- the various String#getBytes methods - these all allocate a new byte array for each invocation
- the underlying java.nio.charset.CharsetEncoder used internally by String - especially method CoderResult encode(CharBuffer in, ByteBuffer out, boolean endOfInput) which converts characters to bytes without object allocation.
The last method is interesting because this gives us an opportunity to also reduce the amount of copying by directly supplying the ByteBuffer buffer used by RandomAccessFileAppender, or the MappedByteBuffer of the MemoryMappedFileAppender.
The resulting API needs to support the fact that implementations may need to call CharsetEncoder#encode multiple times:
- The ByteBuffer may not have enough remaining space to hold all the data; CharsetEncoder#encode returns CoderResult.OVERFLOW to signal this so the caller can consume the contents and reset/clear the buffer before continuing.
- The CharBuffer may not be large enough to hold the full text representation of the log event. Again, CharsetEncoder#encode may need to be invoked multiple times.
(Thinking out loud here, I'm open to suggestions.)
It may be sufficient for the layout interface to have a single additional new method:
Appenders that want to be allocation-free need to implement the ByteBufferDestination interface:
Usage: for example RandomAccessFileAppender code can look like this:
Layout implementation of the writeTo method: layouts need to know how to convert LogEvents to text, but writing this text into the ByteBuffer can be delegated to a helper:
Helper contains utility code for moving the text into a CharBuffer, and for repeatedly calling CharsetEncoder#encode.