LOG4J2-1271 ParameterizedMessage optimizations to avoid or at least postpone 
allocating temporary objects.


Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/dca586cf
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/dca586cf
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/dca586cf

Branch: refs/heads/LOG4J2-1278-gc-free-logger
Commit: dca586cf089ee3f176b5f06e49510639cadd59ec
Parents: 047565e
Author: rpopma <[email protected]>
Authored: Wed Feb 24 02:47:54 2016 +0900
Committer: rpopma <[email protected]>
Committed: Wed Feb 24 02:47:54 2016 +0900

----------------------------------------------------------------------
 .../log4j/message/ParameterizedMessage.java     | 341 +++++----
 .../log4j/message/ParameterizedMessageTest.java |  21 +-
 .../perf/jmh/ParameterizedMessageBenchmark.java |  77 ++
 .../log4j/perf/nogc/ParameterizedMessage.java   | 757 +++++++++++++++++++
 src/changes/changes.xml                         |   3 +
 5 files changed, 1024 insertions(+), 175 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/dca586cf/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
index 334e19b..d315c13 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
@@ -24,6 +24,8 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+
 /**
  * Handles messages that consist of a format string containing '{}' to 
represent each replaceable token, and
  * the parameters.
@@ -32,7 +34,7 @@ import java.util.Set;
  * licensed under the LGPL. It has been relicensed here with his permission 
providing that this attribution remain.
  * </p>
  */
-public class ParameterizedMessage implements Message {
+public class ParameterizedMessage implements Message, StringBuilderFormattable 
{
 
     /**
      * Prefix for recursion.
@@ -68,10 +70,17 @@ public class ParameterizedMessage implements Message {
     private static final char DELIM_STOP = '}';
     private static final char ESCAPE_CHAR = '\\';
 
+    // storing JDK classes in ThreadLocals does not cause memory leaks in web 
apps, so this is okay
+    private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new 
ThreadLocal<>();
+    private static ThreadLocal<SimpleDateFormat> threadLocalSimpleDateFormat = 
new ThreadLocal<>();
+    private static ThreadLocal<Object[]> threadLocalUnrolledArgs = new 
ThreadLocal<>();
+
     private final String messagePattern;
-    private final String[] stringArgs;
+    private final int argCount;
+    private StringBuilder formattedMessage;
     private transient Object[] argArray;
-    private transient String formattedMessage;
+
+    private boolean isThrowableInitialized;
     private transient Throwable throwable;
 
     /**
@@ -80,24 +89,29 @@ public class ParameterizedMessage implements Message {
      * where parameters should be substituted.
      * @param stringArgs The arguments for substitution.
      * @param throwable A Throwable.
+     * @deprecated Use constructor ParameterizedMessage(String, Object[], 
Throwable) instead
      */
     public ParameterizedMessage(final String messagePattern, final String[] 
stringArgs, final Throwable throwable) {
         this.messagePattern = messagePattern;
-        this.stringArgs = stringArgs;
+        this.argArray = stringArgs;
+        this.argCount = stringArgs == null ? 0 : stringArgs.length;
         this.throwable = throwable;
+        this.isThrowableInitialized = throwable != null;
     }
 
     /**
      * Creates a parameterized message.
      * @param messagePattern The message "format" string. This will be a 
String containing "{}" placeholders
      * where parameters should be substituted.
-     * @param objectArgs The arguments for substitution.
+     * @param arguments The arguments for substitution.
      * @param throwable A Throwable.
      */
-    public ParameterizedMessage(final String messagePattern, final Object[] 
objectArgs, final Throwable throwable) {
+    public ParameterizedMessage(final String messagePattern, final Object[] 
arguments, final Throwable throwable) {
         this.messagePattern = messagePattern;
+        this.argArray = arguments;
+        this.argCount = arguments == null ? 0 : arguments.length;
         this.throwable = throwable;
-        this.stringArgs = argumentsToStrings(objectArgs);
+        this.isThrowableInitialized = throwable != null;
     }
 
     /**
@@ -113,7 +127,8 @@ public class ParameterizedMessage implements Message {
      */
     public ParameterizedMessage(final String messagePattern, final Object[] 
arguments) {
         this.messagePattern = messagePattern;
-        this.stringArgs = argumentsToStrings(arguments);
+        this.argArray = arguments;
+        this.argCount = arguments == null ? 0 : arguments.length;
     }
 
     /**
@@ -122,56 +137,40 @@ public class ParameterizedMessage implements Message {
      * @param arg The parameter.
      */
     public ParameterizedMessage(final String messagePattern, final Object arg) 
{
-        this(messagePattern, new Object[]{arg});
+        this.messagePattern = messagePattern;
+        Object[] args = unrolledArgs();
+        args[0] = arg;
+        this.argCount = 1;
     }
 
     /**
      * Constructor with a pattern and two parameters.
      * @param messagePattern The message pattern.
-     * @param arg1 The first parameter.
-     * @param arg2 The second parameter.
+     * @param arg0 The first parameter.
+     * @param arg1 The second parameter.
      */
-    public ParameterizedMessage(final String messagePattern, final Object 
arg1, final Object arg2) {
-        this(messagePattern, new Object[]{arg1, arg2});
+    public ParameterizedMessage(final String messagePattern, final Object 
arg0, final Object arg1) {
+        this.messagePattern = messagePattern;
+        Object[] args = unrolledArgs();
+        args[0] = arg0;
+        args[1] = arg1;
+        this.argCount = 2;
     }
 
-    private String[] argumentsToStrings(final Object[] arguments) {
-        if (arguments == null) {
-            return null;
-        }
-        final int argsCount = countArgumentPlaceholders(messagePattern);
-        int resultArgCount = arguments.length;
-        if (argsCount < arguments.length && throwable == null && 
arguments[arguments.length - 1] instanceof Throwable) {
-            throwable = (Throwable) arguments[arguments.length - 1];
-            resultArgCount--;
+    private static Object[] unrolledArgs() {
+        Object[] result = threadLocalUnrolledArgs.get();
+        if (result == null) {
+            result = new Object[10]; // array must be as big as number of 
unrolled varargs
+            threadLocalUnrolledArgs.set(result);
         }
-        argArray = new Object[resultArgCount];
-        System.arraycopy(arguments, 0, argArray, 0, resultArgCount);
-
-        String[] strArgs;
-        if (argsCount == 1 && throwable == null && arguments.length > 1) {
-            // special case
-            strArgs = new String[1];
-            strArgs[0] = deepToString(arguments);
-        } else {
-            strArgs = new String[resultArgCount];
-            for (int i = 0; i < strArgs.length; i++) {
-                strArgs[i] = deepToString(arguments[i]);
-            }
-        }
-        return strArgs;
+        return result;
     }
 
-    /**
-     * Returns the formatted message.
-     * @return the formatted message.
-     */
-    @Override
-    public String getFormattedMessage() {
-        if (formattedMessage == null) {
-            formattedMessage = formatMessage(messagePattern, stringArgs);
+    private void clearUnrolledArgs() {
+        final Object[] args = unrolledArgs();
+        for (int i = 0; i < argCount; i++) {
+            args[i] = null;
         }
-        return formattedMessage;
     }
 
     /**
@@ -189,10 +188,7 @@ public class ParameterizedMessage implements Message {
      */
     @Override
     public Object[] getParameters() {
-        if (argArray != null) {
-            return argArray;
-        }
-        return stringArgs;
+        return argArray == null && argCount > 0 ? unrolledArgs() : argArray;
     }
 
     /**
@@ -206,40 +202,52 @@ public class ParameterizedMessage implements Message {
      */
     @Override
     public Throwable getThrowable() {
+        if (!isThrowableInitialized) {
+            initFormattedMessage(); // force initialization
+        }
         return throwable;
     }
 
-    protected String formatMessage(final String msgPattern, final String[] 
sArgs) {
-        return formatStringArgs(msgPattern, sArgs);
-    }
-
+    /**
+     * Returns the formatted message.
+     * @return the formatted message.
+     */
     @Override
-    public boolean equals(final Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
+    public String getFormattedMessage() {
+        if (formattedMessage == null) {
+            initFormattedMessage();
         }
+        return formattedMessage.toString();
+    }
 
-        final ParameterizedMessage that = (ParameterizedMessage) o;
+    private void initFormattedMessage() {
+        final StringBuilder buffer = getThreadLocalStringBuilder();
+        formatTo(buffer);
+        formattedMessage = buffer;
+    }
 
-        if (messagePattern != null ? 
!messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
-            return false;
-        }
-        if (!Arrays.equals(stringArgs, that.stringArgs)) {
-            return false;
+    private static StringBuilder getThreadLocalStringBuilder() {
+        StringBuilder buffer = threadLocalStringBuilder.get();
+        if (buffer == null) {
+            buffer = new StringBuilder(255);
+            threadLocalStringBuilder.set(buffer);
         }
-        //if (throwable != null ? !throwable.equals(that.throwable) : 
that.throwable != null) return false;
-
-        return true;
+        buffer.setLength(0);
+        return buffer;
     }
 
     @Override
-    public int hashCode() {
-        int result = messagePattern != null ? messagePattern.hashCode() : 0;
-        result = HASHVAL * result + (stringArgs != null ? 
Arrays.hashCode(stringArgs) : 0);
-        return result;
+    public void formatTo(final StringBuilder buffer) {
+        final Throwable t = formatMessage(buffer, messagePattern, 
getParameters(), argCount, throwable);
+        initThrowable(t);
+        clearUnrolledArgs();
+    }
+
+    private void initThrowable(final Throwable t) {
+        if (!isThrowableInitialized && t != null) {
+            throwable = t;
+        }
+        isThrowableInitialized = true;
     }
 
     /**
@@ -250,48 +258,31 @@ public class ParameterizedMessage implements Message {
      * @return the formatted message.
      */
     public static String format(final String messagePattern, final Object[] 
arguments) {
-        if (messagePattern == null || arguments == null || arguments.length == 
0) {
-            return messagePattern;
-        }
-        if (arguments instanceof String[]) {
-            return formatStringArgs(messagePattern, (String[]) arguments);
-        }
-        final String[] stringArgs = new String[arguments.length];
-        for (int i = 0; i < arguments.length; i++) {
-            stringArgs[i] = String.valueOf(arguments[i]);
-        }
-        return formatStringArgs(messagePattern, stringArgs);
+        final StringBuilder result = getThreadLocalStringBuilder();
+        final int argCount = arguments == null ? 0 : arguments.length;
+        formatMessage(result, messagePattern, arguments, argCount, null);
+        return result.toString();
     }
 
     /**
      * Replace placeholders in the given messagePattern with arguments.
-     * <p>
-     * Package protected for unit tests.
      *
+     * @param buffer the buffer to write the formatted message into
      * @param messagePattern the message pattern containing placeholders.
      * @param arguments      the arguments to be used to replace placeholders.
-     * @return the formatted message.
+     * @param err the Throwable passed to the constructor (or null if none)
+     * @return the Throwable that was the last unused argument in the list, or 
the specified {@code err} Throwable
      */
-    // Profiling showed this method is important to log4j performance. Modify 
with care!
-    // 33 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    static String formatStringArgs(final String messagePattern, final String[] 
arguments) {
-        int len = 0;
-        if (messagePattern == null || (len = messagePattern.length()) == 0 || 
arguments == null
-                || arguments.length == 0) {
-            return messagePattern;
+    private static Throwable formatMessage(final StringBuilder buffer, final 
String messagePattern,
+            final Object[] arguments, final int argCount, final Throwable err) 
{
+        if (messagePattern == null || arguments == null || argCount == 0) {
+            buffer.append(messagePattern);
+            return err;
         }
-
-        return formatStringArgs0(messagePattern, len, arguments);
-    }
-
-    // Profiling showed this method is important to log4j performance. Modify 
with care!
-    // 157 bytes (will be inlined when hot enough: < 325 bytes)
-    private static String formatStringArgs0(final String messagePattern, final 
int len, final String[] arguments) {
-        final char[] result = new char[len + sumStringLengths(arguments)];
-        int pos = 0;
         int escapeCounter = 0;
         int currentArgument = 0;
         int i = 0;
+        int len = messagePattern.length();
         for (; i < len - 1; i++) { // last char is excluded from the loop
             final char curChar = messagePattern.charAt(i);
             if (curChar == ESCAPE_CHAR) {
@@ -301,38 +292,32 @@ public class ParameterizedMessage implements Message {
                     i++;
 
                     // write escaped escape chars
-                    pos = writeEscapedEscapeChars(escapeCounter, result, pos);
+                    writeEscapedEscapeChars(escapeCounter, buffer);
 
                     if (isOdd(escapeCounter)) {
-                        // i.e. escaped
-                        // write escaped escape chars
-                        pos = writeDelimPair(result, pos);
+                        // i.e. escaped: write escaped escape chars
+                        writeDelimPair(buffer);
                     } else {
                         // unescaped
-                        pos = writeArgOrDelimPair(arguments, currentArgument, 
result, pos);
+                        writeArgOrDelimPair(arguments, argCount, 
currentArgument, buffer);
                         currentArgument++;
                     }
                 } else {
-                    pos = handleLiteralChar(result, pos, escapeCounter, 
curChar);
+                    handleLiteralChar(buffer, escapeCounter, curChar);
                 }
                 escapeCounter = 0;
             }
         }
-        pos = handleRemainingCharIfAny(messagePattern, len, result, pos, 
escapeCounter, i);
-        return new String(result, 0, pos);
+        handleRemainingCharIfAny(messagePattern, len, buffer, escapeCounter, 
i);
+        return extractThrowable(arguments, argCount, currentArgument, err);
     }
 
-    /**
-     * Returns the sum of the lengths of all Strings in the specified array.
-     */
-    // Profiling showed this method is important to log4j performance. Modify 
with care!
-    // 30 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int sumStringLengths(final String[] arguments) {
-        int result = 0;
-        for (int i = 0; i < arguments.length; i++) {
-            result += String.valueOf(arguments[i]).length();
+    private static Throwable extractThrowable(final Object[] params, final int 
paramCount, final int usedParams,
+            final Throwable err) {
+        if (usedParams < paramCount && err == null && params[paramCount - 1] 
instanceof Throwable) {
+            return (Throwable) params[paramCount - 1];
         }
-        return result;
+        return err;
     }
 
     /**
@@ -351,13 +336,12 @@ public class ParameterizedMessage implements Message {
      */
     // Profiling showed this method is important to log4j performance. Modify 
with care!
     // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int handleRemainingCharIfAny(final String messagePattern, 
final int len, final char[] result,
-            int pos, int escapeCounter, int i) {
+    private static void handleRemainingCharIfAny(final String messagePattern, 
final int len,
+            final StringBuilder buffer, int escapeCounter, int i) {
         if (i == len - 1) {
             final char curChar = messagePattern.charAt(i);
-            pos = handleLastChar(result, pos, escapeCounter, curChar);
+            handleLastChar(buffer, escapeCounter, curChar);
         }
-        return pos;
     }
 
     /**
@@ -365,13 +349,12 @@ public class ParameterizedMessage implements Message {
      */
     // Profiling showed this method is important to log4j performance. Modify 
with care!
     // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int handleLastChar(final char[] result, int pos, final int 
escapeCounter, final char curChar) {
+    private static void handleLastChar(final StringBuilder buffer, final int 
escapeCounter, final char curChar) {
         if (curChar == ESCAPE_CHAR) {
-            pos = writeUnescapedEscapeChars(escapeCounter + 1, result, pos);
+            writeUnescapedEscapeChars(escapeCounter + 1, buffer);
         } else {
-            pos = handleLiteralChar(result, pos, escapeCounter, curChar);
+            handleLiteralChar(buffer, escapeCounter, curChar);
         }
-        return pos;
     }
 
     /**
@@ -380,12 +363,11 @@ public class ParameterizedMessage implements Message {
      */
     // Profiling showed this method is important to log4j performance. Modify 
with care!
     // 16 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int handleLiteralChar(final char[] result, int pos, final 
int escapeCounter, final char curChar) {
+    private static void handleLiteralChar(final StringBuilder buffer, final 
int escapeCounter, final char curChar) {
         // any other char beside ESCAPE or DELIM_START/STOP-combo
         // write unescaped escape chars
-        pos = writeUnescapedEscapeChars(escapeCounter, result, pos);
-        result[pos++] = curChar;
-        return pos;
+        writeUnescapedEscapeChars(escapeCounter, buffer);
+        buffer.append(curChar);
     }
 
     /**
@@ -393,10 +375,9 @@ public class ParameterizedMessage implements Message {
      */
     // Profiling showed this method is important to log4j performance. Modify 
with care!
     // 18 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int writeDelimPair(final char[] result, int pos) {
-        result[pos++] = DELIM_START;
-        result[pos++] = DELIM_STOP;
-        return pos;
+    private static void writeDelimPair(final StringBuilder buffer) {
+        buffer.append(DELIM_START);
+        buffer.append(DELIM_STOP);
     }
 
     /**
@@ -414,9 +395,9 @@ public class ParameterizedMessage implements Message {
      */
     // Profiling showed this method is important to log4j performance. Modify 
with care!
     // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int writeEscapedEscapeChars(final int escapeCounter, final 
char[] result, final int pos) {
+    private static void writeEscapedEscapeChars(final int escapeCounter, final 
StringBuilder buffer) {
         final int escapedEscapes = escapeCounter >> 1; // divide by two
-        return writeUnescapedEscapeChars(escapedEscapes, result, pos);
+        writeUnescapedEscapeChars(escapedEscapes, buffer);
     }
 
     /**
@@ -425,12 +406,11 @@ public class ParameterizedMessage implements Message {
      */
     // Profiling showed this method is important to log4j performance. Modify 
with care!
     // 20 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int writeUnescapedEscapeChars(int escapeCounter, char[] 
result, int pos) {
+    private static void writeUnescapedEscapeChars(int escapeCounter, final 
StringBuilder buffer) {
         while (escapeCounter > 0) {
-            result[pos++] = ESCAPE_CHAR;
+            buffer.append(ESCAPE_CHAR);
             escapeCounter--;
         }
-        return pos;
     }
 
     /**
@@ -439,28 +419,42 @@ public class ParameterizedMessage implements Message {
      */
     // Profiling showed this method is important to log4j performance. Modify 
with care!
     // 25 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int writeArgOrDelimPair(final String[] arguments, final int 
currentArgument, final char[] result,
-            int pos) {
-        if (currentArgument < arguments.length) {
-            pos = writeArgAt0(arguments, currentArgument, result, pos);
+    private static void writeArgOrDelimPair(final Object[] arguments, final 
int argCount, final int currentArgument,
+            final StringBuilder buffer) {
+        if (currentArgument < argCount) {
+            recursiveDeepToString(arguments[currentArgument], buffer, null);
         } else {
-            pos = writeDelimPair(result, pos);
+            writeDelimPair(buffer);
         }
-        return pos;
     }
 
-    /**
-     * Appends the argument at the specified argument index to the specified 
result char array at the specified position
-     * and returns the resulting position.
-     */
-    // Profiling showed this method is important to log4j performance. Modify 
with care!
-    // 30 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
-    private static int writeArgAt0(final String[] arguments, final int 
currentArgument, final char[] result,
-            final int pos) {
-        final String arg = String.valueOf(arguments[currentArgument]);
-        int argLen = arg.length();
-        arg.getChars(0, argLen, result, pos);
-        return pos + argLen;
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final ParameterizedMessage that = (ParameterizedMessage) o;
+
+        if (messagePattern != null ? 
!messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
+            return false;
+        }
+        if (!Arrays.equals(this.argArray, that.argArray)) {
+            return false;
+        }
+        //if (throwable != null ? !throwable.equals(that.throwable) : 
that.throwable != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = messagePattern != null ? messagePattern.hashCode() : 0;
+        result = HASHVAL * result + (argArray != null ? 
Arrays.hashCode(argArray) : 0);
+        return result;
     }
 
     /**
@@ -468,6 +462,7 @@ public class ParameterizedMessage implements Message {
      *
      * @param messagePattern the message pattern to be analyzed.
      * @return the number of unescaped placeholders.
+     * @deprecated This method is no longer used internally and is scheduled 
to be deleted.
      */
     public static int countArgumentPlaceholders(final String messagePattern) {
         if (messagePattern == null) {
@@ -555,7 +550,7 @@ public class ParameterizedMessage implements Message {
      * @param dejaVu a list of container identities that were already used.
      */
     private static void recursiveDeepToString(final Object o, final 
StringBuilder str, final Set<String> dejaVu) {
-        if (appendStringDateOrNull(o, str)) {
+        if (appendSpecialTypes(o, str)) {
             return;
         }
         if (isMaybeRecursive(o)) {
@@ -565,9 +560,15 @@ public class ParameterizedMessage implements Message {
         }
     }
 
-    private static boolean appendStringDateOrNull(final Object o, final 
StringBuilder str) {
+    private static boolean appendSpecialTypes(final Object o, final 
StringBuilder str) {
         if (o == null || o instanceof String) {
-            str.append(String.valueOf(o));
+            str.append((String) o);
+            return true;
+        } else if (o instanceof StringBuilder) {
+            str.append((StringBuilder) o);
+            return true;
+        } else if (o instanceof StringBuilderFormattable) {
+            ((StringBuilderFormattable) o).formatTo(str);
             return true;
         }
         return appendDate(o, str);
@@ -584,8 +585,12 @@ public class ParameterizedMessage implements Message {
     }
 
     private static SimpleDateFormat getSimpleDateFormat() {
-        // I'll leave it like this for the moment... this could probably be 
optimized using ThreadLocal...
-        return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+        SimpleDateFormat result = threadLocalSimpleDateFormat.get();
+        if (result == null) {
+            result = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+            threadLocalSimpleDateFormat.set(result);
+        }
+        return result;
     }
 
     /**
@@ -595,8 +600,10 @@ public class ParameterizedMessage implements Message {
         return o.getClass().isArray() || o instanceof Map || o instanceof 
Collection;
     }
 
-    private static void appendPotentiallyRecursiveValue(final Object o, final 
StringBuilder str,
-            final Set<String> dejaVu) {
+    private static void appendPotentiallyRecursiveValue(final Object o, final 
StringBuilder str, Set<String> dejaVu) {
+        if (dejaVu == null) {
+            dejaVu = new HashSet<>();
+        }
         final Class<?> oClass = o.getClass();
         if (oClass.isArray()) {
             appendArray(o, str, dejaVu, oClass);

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/dca586cf/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java
 
b/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java
index d4ba0de..4c20175 100644
--- 
a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java
+++ 
b/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java
@@ -41,7 +41,7 @@ public class ParameterizedMessageTest {
     public void testFormat3StringArgs() {
         final String testMsg = "Test message {}{} {}";
         final String[] args = { "a", "b", "c" };
-        final String result = ParameterizedMessage.formatStringArgs(testMsg, 
args);
+        final String result = ParameterizedMessage.format(testMsg, args);
         assertEquals("Test message ab c", result);
     }
 
@@ -49,7 +49,7 @@ public class ParameterizedMessageTest {
     public void testFormatNullArgs() {
         final String testMsg = "Test message {} {} {} {} {} {}";
         final String[] args = { "a", null, "c", null, null, null };
-        final String result = ParameterizedMessage.formatStringArgs(testMsg, 
args);
+        final String result = ParameterizedMessage.format(testMsg, args);
         assertEquals("Test message a null c null null null", result);
     }
 
@@ -57,7 +57,7 @@ public class ParameterizedMessageTest {
     public void testFormatStringArgsIgnoresSuperfluousArgs() {
         final String testMsg = "Test message {}{} {}";
         final String[] args = { "a", "b", "c", "unnecessary", "superfluous" };
-        final String result = ParameterizedMessage.formatStringArgs(testMsg, 
args);
+        final String result = ParameterizedMessage.format(testMsg, args);
         assertEquals("Test message ab c", result);
     }
 
@@ -65,7 +65,7 @@ public class ParameterizedMessageTest {
     public void testFormatStringArgsWithEscape() {
         final String testMsg = "Test message \\{}{} {}";
         final String[] args = { "a", "b", "c" };
-        final String result = ParameterizedMessage.formatStringArgs(testMsg, 
args);
+        final String result = ParameterizedMessage.format(testMsg, args);
         assertEquals("Test message {}a b", result);
     }
 
@@ -73,7 +73,7 @@ public class ParameterizedMessageTest {
     public void testFormatStringArgsWithTrailingEscape() {
         final String testMsg = "Test message {}{} {}\\";
         final String[] args = { "a", "b", "c" };
-        final String result = ParameterizedMessage.formatStringArgs(testMsg, 
args);
+        final String result = ParameterizedMessage.format(testMsg, args);
         assertEquals("Test message ab c\\", result);
     }
 
@@ -81,7 +81,7 @@ public class ParameterizedMessageTest {
     public void testFormatStringArgsWithTrailingEscapedEscape() {
         final String testMsg = "Test message {}{} {}\\\\";
         final String[] args = { "a", "b", "c" };
-        final String result = ParameterizedMessage.formatStringArgs(testMsg, 
args);
+        final String result = ParameterizedMessage.format(testMsg, args);
         assertEquals("Test message ab c\\\\", result);
     }
 
@@ -89,7 +89,7 @@ public class ParameterizedMessageTest {
     public void testFormatStringArgsWithEscapedEscape() {
         final String testMsg = "Test message \\\\{}{} {}";
         final String[] args = { "a", "b", "c" };
-        final String result = ParameterizedMessage.formatStringArgs(testMsg, 
args);
+        final String result = ParameterizedMessage.format(testMsg, args);
         assertEquals("Test message \\ab c", result);
     }
 
@@ -102,6 +102,11 @@ public class ParameterizedMessageTest {
         // modify parameter before calling msg.getFormattedMessage
         param.set("XYZ");
         final String actual = msg.getFormattedMessage();
-        assertEquals("Should use initial param value", "Test message abc", 
actual);
+        assertEquals("Should use current param value", "Test message XYZ", 
actual);
+
+        // modify parameter after calling msg.getFormattedMessage
+        param.set("000");
+        final String after = msg.getFormattedMessage();
+        assertEquals("Should not change after rendered once", "Test message 
XYZ", after);
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/dca586cf/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ParameterizedMessageBenchmark.java
----------------------------------------------------------------------
diff --git 
a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ParameterizedMessageBenchmark.java
 
b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ParameterizedMessageBenchmark.java
new file mode 100644
index 0000000..463ba92
--- /dev/null
+++ 
b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ParameterizedMessageBenchmark.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.perf.jmh;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.message.ParameterizedMessage;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ */
+// ============================== HOW TO RUN THIS TEST: 
====================================
+//
+// single thread:
+// java -jar log4j-perf/target/benchmarks.jar ".*ParameterizedMessageBench.*" 
-f 1 -wi 5 -i 10
+//
+// multiple threads (for example, 4 threads):
+// java -jar log4j-perf/target/benchmarks.jar ".*ParameterizedMessageBench.*" 
-f 1 -wi 5 -i 10 -t 4 -si true
+//
+// Usage help:
+// java -jar log4j-perf/target/benchmarks.jar -help
+//
+@State(Scope.Benchmark)
+public class ParameterizedMessageBenchmark {
+    private static final Object[] ARGS = { "arg1", "arg2" };
+
+    @State(Scope.Thread)
+    public static class ThreadState {
+        StringBuilder buffer = new StringBuilder(256);
+    }
+
+    @Benchmark
+    @BenchmarkMode(Mode.SampleTime)
+    @OutputTimeUnit(TimeUnit.NANOSECONDS)
+    public String classicGetFormattedMsg() {
+        return new org.apache.logging.log4j.perf.nogc.ParameterizedMessage(
+                "pattern {} with {} two parameters and some text", 
ARGS).getFormattedMessage();
+    }
+
+    @Benchmark
+    @BenchmarkMode(Mode.SampleTime)
+    @OutputTimeUnit(TimeUnit.NANOSECONDS)
+    public String refactoredGetFormattedMsg() {
+        return new ParameterizedMessage("pattern {} with {} two parameters and 
some text", ARGS).
+                getFormattedMessage();
+    }
+
+    @Benchmark
+    @BenchmarkMode(Mode.SampleTime)
+    @OutputTimeUnit(TimeUnit.NANOSECONDS)
+    public int refactoredFormatTo(ThreadState state) {
+        state.buffer.setLength(0);
+        new ParameterizedMessage("pattern {} with {} two parameters and some 
text", ARGS).
+                formatTo(state.buffer);
+        return state.buffer.length();
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/dca586cf/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/ParameterizedMessage.java
----------------------------------------------------------------------
diff --git 
a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/ParameterizedMessage.java
 
b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/ParameterizedMessage.java
new file mode 100644
index 0000000..be45a60
--- /dev/null
+++ 
b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/ParameterizedMessage.java
@@ -0,0 +1,757 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.perf.nogc;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.logging.log4j.message.Message;
+
+/**
+ * Handles messages that consist of a format string containing '{}' to 
represent each replaceable token, and
+ * the parameters.
+ * <p>
+ * This class was originally written for <a 
href="http://lilithapp.com/";>Lilith</a> by Joern Huxhorn where it is
+ * licensed under the LGPL. It has been relicensed here with his permission 
providing that this attribution remain.
+ * </p>
+ */
+public class ParameterizedMessage implements Message {
+
+    /**
+     * Prefix for recursion.
+     */
+    public static final String RECURSION_PREFIX = "[...";
+    /**
+     * Suffix for recursion.
+     */
+    public static final String RECURSION_SUFFIX = "...]";
+
+    /**
+     * Prefix for errors.
+     */
+    public static final String ERROR_PREFIX = "[!!!";
+    /**
+     * Separator for errors.
+     */
+    public static final String ERROR_SEPARATOR = "=>";
+    /**
+     * Separator for error messages.
+     */
+    public static final String ERROR_MSG_SEPARATOR = ":";
+    /**
+     * Suffix for errors.
+     */
+    public static final String ERROR_SUFFIX = "!!!]";
+
+    private static final long serialVersionUID = -665975803997290697L;
+
+    private static final int HASHVAL = 31;
+
+    private static final char DELIM_START = '{';
+    private static final char DELIM_STOP = '}';
+    private static final char ESCAPE_CHAR = '\\';
+
+    private final String messagePattern;
+    private final String[] stringArgs;
+    private transient Object[] argArray;
+    private transient String formattedMessage;
+    private transient Throwable throwable;
+
+    /**
+     * Creates a parameterized message.
+     * @param messagePattern The message "format" string. This will be a 
String containing "{}" placeholders
+     * where parameters should be substituted.
+     * @param stringArgs The arguments for substitution.
+     * @param throwable A Throwable.
+     */
+    public ParameterizedMessage(final String messagePattern, final String[] 
stringArgs, final Throwable throwable) {
+        this.messagePattern = messagePattern;
+        this.stringArgs = stringArgs;
+        this.throwable = throwable;
+    }
+
+    /**
+     * Creates a parameterized message.
+     * @param messagePattern The message "format" string. This will be a 
String containing "{}" placeholders
+     * where parameters should be substituted.
+     * @param objectArgs The arguments for substitution.
+     * @param throwable A Throwable.
+     */
+    public ParameterizedMessage(final String messagePattern, final Object[] 
objectArgs, final Throwable throwable) {
+        this.messagePattern = messagePattern;
+        this.throwable = throwable;
+        this.stringArgs = argumentsToStrings(objectArgs);
+    }
+
+    /**
+     * Constructs a ParameterizedMessage which contains the arguments 
converted to String as well as an optional
+     * Throwable.
+     *
+     * <p>If the last argument is a Throwable and is NOT used up by a 
placeholder in the message pattern it is returned
+     * in {@link #getThrowable()} and won't be contained in the created 
String[].
+     * If it is used up {@link #getThrowable()} will return null even if the 
last argument was a Throwable!</p>
+     *
+     * @param messagePattern the message pattern that to be checked for 
placeholders.
+     * @param arguments      the argument array to be converted.
+     */
+    public ParameterizedMessage(final String messagePattern, final Object[] 
arguments) {
+        this.messagePattern = messagePattern;
+        this.stringArgs = argumentsToStrings(arguments);
+    }
+
+    /**
+     * Constructor with a pattern and a single parameter.
+     * @param messagePattern The message pattern.
+     * @param arg The parameter.
+     */
+    public ParameterizedMessage(final String messagePattern, final Object arg) 
{
+        this(messagePattern, new Object[]{arg});
+    }
+
+    /**
+     * Constructor with a pattern and two parameters.
+     * @param messagePattern The message pattern.
+     * @param arg1 The first parameter.
+     * @param arg2 The second parameter.
+     */
+    public ParameterizedMessage(final String messagePattern, final Object 
arg1, final Object arg2) {
+        this(messagePattern, new Object[]{arg1, arg2});
+    }
+
+    private String[] argumentsToStrings(final Object[] arguments) {
+        if (arguments == null) {
+            return null;
+        }
+        final int argsCount = countArgumentPlaceholders(messagePattern);
+        int resultArgCount = arguments.length;
+        if (argsCount < arguments.length && throwable == null && 
arguments[arguments.length - 1] instanceof Throwable) {
+            throwable = (Throwable) arguments[arguments.length - 1];
+            resultArgCount--;
+        }
+        argArray = new Object[resultArgCount];
+        System.arraycopy(arguments, 0, argArray, 0, resultArgCount);
+
+        String[] strArgs;
+        if (argsCount == 1 && throwable == null && arguments.length > 1) {
+            // special case
+            strArgs = new String[1];
+            strArgs[0] = deepToString(arguments);
+        } else {
+            strArgs = new String[resultArgCount];
+            for (int i = 0; i < strArgs.length; i++) {
+                strArgs[i] = deepToString(arguments[i]);
+            }
+        }
+        return strArgs;
+    }
+
+    /**
+     * Returns the formatted message.
+     * @return the formatted message.
+     */
+    @Override
+    public String getFormattedMessage() {
+        if (formattedMessage == null) {
+            formattedMessage = formatMessage(messagePattern, stringArgs);
+        }
+        return formattedMessage;
+    }
+
+    /**
+     * Returns the message pattern.
+     * @return the message pattern.
+     */
+    @Override
+    public String getFormat() {
+        return messagePattern;
+    }
+
+    /**
+     * Returns the message parameters.
+     * @return the message parameters.
+     */
+    @Override
+    public Object[] getParameters() {
+        if (argArray != null) {
+            return argArray;
+        }
+        return stringArgs;
+    }
+
+    /**
+     * Returns the Throwable that was given as the last argument, if any.
+     * It will not survive serialization. The Throwable exists as part of the 
message
+     * primarily so that it can be extracted from the end of the list of 
parameters
+     * and then be added to the LogEvent. As such, the Throwable in the event 
should
+     * not be used once the LogEvent has been constructed.
+     *
+     * @return the Throwable, if any.
+     */
+    @Override
+    public Throwable getThrowable() {
+        return throwable;
+    }
+
+    protected String formatMessage(final String msgPattern, final String[] 
sArgs) {
+        return formatStringArgs(msgPattern, sArgs);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final ParameterizedMessage that = (ParameterizedMessage) o;
+
+        if (messagePattern != null ? 
!messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
+            return false;
+        }
+        if (!Arrays.equals(stringArgs, that.stringArgs)) {
+            return false;
+        }
+        //if (throwable != null ? !throwable.equals(that.throwable) : 
that.throwable != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = messagePattern != null ? messagePattern.hashCode() : 0;
+        result = HASHVAL * result + (stringArgs != null ? 
Arrays.hashCode(stringArgs) : 0);
+        return result;
+    }
+
+    /**
+     * Replace placeholders in the given messagePattern with arguments.
+     *
+     * @param messagePattern the message pattern containing placeholders.
+     * @param arguments      the arguments to be used to replace placeholders.
+     * @return the formatted message.
+     */
+    public static String format(final String messagePattern, final Object[] 
arguments) {
+        if (messagePattern == null || arguments == null || arguments.length == 
0) {
+            return messagePattern;
+        }
+        if (arguments instanceof String[]) {
+            return formatStringArgs(messagePattern, (String[]) arguments);
+        }
+        final String[] stringArgs = new String[arguments.length];
+        for (int i = 0; i < arguments.length; i++) {
+            stringArgs[i] = String.valueOf(arguments[i]);
+        }
+        return formatStringArgs(messagePattern, stringArgs);
+    }
+
+    /**
+     * Replace placeholders in the given messagePattern with arguments.
+     * <p>
+     * Package protected for unit tests.
+     *
+     * @param messagePattern the message pattern containing placeholders.
+     * @param arguments      the arguments to be used to replace placeholders.
+     * @return the formatted message.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 33 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    static String formatStringArgs(final String messagePattern, final String[] 
arguments) {
+        int len = 0;
+        if (messagePattern == null || (len = messagePattern.length()) == 0 || 
arguments == null
+                || arguments.length == 0) {
+            return messagePattern;
+        }
+
+        return formatStringArgs0(messagePattern, len, arguments);
+    }
+
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 157 bytes (will be inlined when hot enough: < 325 bytes)
+    private static String formatStringArgs0(final String messagePattern, final 
int len, final String[] arguments) {
+        final char[] result = new char[len + sumStringLengths(arguments)];
+        int pos = 0;
+        int escapeCounter = 0;
+        int currentArgument = 0;
+        int i = 0;
+        for (; i < len - 1; i++) { // last char is excluded from the loop
+            final char curChar = messagePattern.charAt(i);
+            if (curChar == ESCAPE_CHAR) {
+                escapeCounter++;
+            } else {
+                if (isDelimPair(curChar, messagePattern, i)) { // looks ahead 
one char
+                    i++;
+
+                    // write escaped escape chars
+                    pos = writeEscapedEscapeChars(escapeCounter, result, pos);
+
+                    if (isOdd(escapeCounter)) {
+                        // i.e. escaped
+                        // write escaped escape chars
+                        pos = writeDelimPair(result, pos);
+                    } else {
+                        // unescaped
+                        pos = writeArgOrDelimPair(arguments, currentArgument, 
result, pos);
+                        currentArgument++;
+                    }
+                } else {
+                    pos = handleLiteralChar(result, pos, escapeCounter, 
curChar);
+                }
+                escapeCounter = 0;
+            }
+        }
+        pos = handleRemainingCharIfAny(messagePattern, len, result, pos, 
escapeCounter, i);
+        return new String(result, 0, pos);
+    }
+
+    /**
+     * Returns the sum of the lengths of all Strings in the specified array.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 30 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int sumStringLengths(final String[] arguments) {
+        int result = 0;
+        for (int i = 0; i < arguments.length; i++) {
+            result += String.valueOf(arguments[i]).length();
+        }
+        return result;
+    }
+
+    /**
+     * Returns {@code true} if the specified char and the char at {@code 
curCharIndex + 1} in the specified message
+     * pattern together form a "{}" delimiter pair, returns {@code false} 
otherwise.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 22 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static boolean isDelimPair(final char curChar, final String 
messagePattern, final int curCharIndex) {
+        return curChar == DELIM_START && messagePattern.charAt(curCharIndex + 
1) == DELIM_STOP;
+    }
+
+    /**
+     * Detects whether the message pattern has been fully processed or if an 
unprocessed character remains and processes
+     * it if necessary, returning the resulting position in the result char 
array.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int handleRemainingCharIfAny(final String messagePattern, 
final int len, final char[] result,
+            int pos, int escapeCounter, int i) {
+        if (i == len - 1) {
+            final char curChar = messagePattern.charAt(i);
+            pos = handleLastChar(result, pos, escapeCounter, curChar);
+        }
+        return pos;
+    }
+
+    /**
+     * Processes the last unprocessed character and returns the resulting 
position in the result char array.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int handleLastChar(final char[] result, int pos, final int 
escapeCounter, final char curChar) {
+        if (curChar == ESCAPE_CHAR) {
+            pos = writeUnescapedEscapeChars(escapeCounter + 1, result, pos);
+        } else {
+            pos = handleLiteralChar(result, pos, escapeCounter, curChar);
+        }
+        return pos;
+    }
+
+    /**
+     * Processes a literal char (neither an '\' escape char nor a "{}" 
delimiter pair) and returns the resulting
+     * position.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 16 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int handleLiteralChar(final char[] result, int pos, final 
int escapeCounter, final char curChar) {
+        // any other char beside ESCAPE or DELIM_START/STOP-combo
+        // write unescaped escape chars
+        pos = writeUnescapedEscapeChars(escapeCounter, result, pos);
+        result[pos++] = curChar;
+        return pos;
+    }
+
+    /**
+     * Writes "{}" to the specified result array at the specified position and 
returns the resulting position.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 18 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int writeDelimPair(final char[] result, int pos) {
+        result[pos++] = DELIM_START;
+        result[pos++] = DELIM_STOP;
+        return pos;
+    }
+
+    /**
+     * Returns {@code true} if the specified parameter is odd.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static boolean isOdd(final int number) {
+        return (number & 1) == 1;
+    }
+
+    /**
+     * Writes a '\' char to the specified result array (starting at the 
specified position) for each <em>pair</em> of
+     * '\' escape chars encountered in the message format and returns the 
resulting position.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int writeEscapedEscapeChars(final int escapeCounter, final 
char[] result, final int pos) {
+        final int escapedEscapes = escapeCounter >> 1; // divide by two
+        return writeUnescapedEscapeChars(escapedEscapes, result, pos);
+    }
+
+    /**
+     * Writes the specified number of '\' chars to the specified result array 
(starting at the specified position) and
+     * returns the resulting position.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 20 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int writeUnescapedEscapeChars(int escapeCounter, char[] 
result, int pos) {
+        while (escapeCounter > 0) {
+            result[pos++] = ESCAPE_CHAR;
+            escapeCounter--;
+        }
+        return pos;
+    }
+
+    /**
+     * Appends the argument at the specified argument index (or, if no such 
argument exists, the "{}" delimiter pair) to
+     * the specified result char array at the specified position and returns 
the resulting position.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 25 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int writeArgOrDelimPair(final String[] arguments, final int 
currentArgument, final char[] result,
+            int pos) {
+        if (currentArgument < arguments.length) {
+            pos = writeArgAt0(arguments, currentArgument, result, pos);
+        } else {
+            pos = writeDelimPair(result, pos);
+        }
+        return pos;
+    }
+
+    /**
+     * Appends the argument at the specified argument index to the specified 
result char array at the specified position
+     * and returns the resulting position.
+     */
+    // Profiling showed this method is important to log4j performance. Modify 
with care!
+    // 30 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096
+    private static int writeArgAt0(final String[] arguments, final int 
currentArgument, final char[] result,
+            final int pos) {
+        final String arg = String.valueOf(arguments[currentArgument]);
+        int argLen = arg.length();
+        arg.getChars(0, argLen, result, pos);
+        return pos + argLen;
+    }
+
+    /**
+     * Counts the number of unescaped placeholders in the given messagePattern.
+     *
+     * @param messagePattern the message pattern to be analyzed.
+     * @return the number of unescaped placeholders.
+     */
+    public static int countArgumentPlaceholders(final String messagePattern) {
+        if (messagePattern == null) {
+            return 0;
+        }
+
+        final int delim = messagePattern.indexOf(DELIM_START);
+
+        if (delim == -1) {
+            // special case, no placeholders at all.
+            return 0;
+        }
+        int result = 0;
+        boolean isEscaped = false;
+        for (int i = 0; i < messagePattern.length(); i++) {
+            final char curChar = messagePattern.charAt(i);
+            if (curChar == ESCAPE_CHAR) {
+                isEscaped = !isEscaped;
+            } else if (curChar == DELIM_START) {
+                if (!isEscaped && i < messagePattern.length() - 1 && 
messagePattern.charAt(i + 1) == DELIM_STOP) {
+                    result++;
+                    i++;
+                }
+                isEscaped = false;
+            } else {
+                isEscaped = false;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * This method performs a deep toString of the given Object.
+     * Primitive arrays are converted using their respective Arrays.toString 
methods while
+     * special handling is implemented for "container types", i.e. Object[], 
Map and Collection because those could
+     * contain themselves.
+     * <p>
+     * It should be noted that neither AbstractMap.toString() nor 
AbstractCollection.toString() implement such a
+     * behavior. They only check if the container is directly contained in 
itself, but not if a contained container
+     * contains the original one. Because of that, Arrays.toString(Object[]) 
isn't safe either.
+     * Confusing? Just read the last paragraph again and check the respective 
toString() implementation.
+     * </p>
+     * <p>
+     * This means, in effect, that logging would produce a usable output even 
if an ordinary System.out.println(o)
+     * would produce a relatively hard-to-debug StackOverflowError.
+     * </p>
+     * @param o The object.
+     * @return The String representation.
+     */
+    public static String deepToString(final Object o) {
+        if (o == null) {
+            return null;
+        }
+        if (o instanceof String) {
+            return (String) o;
+        }
+        final StringBuilder str = new StringBuilder();
+        final Set<String> dejaVu = new HashSet<>(); // that's actually a neat 
name ;)
+        recursiveDeepToString(o, str, dejaVu);
+        return str.toString();
+    }
+
+    /**
+     * This method performs a deep toString of the given Object.
+     * Primitive arrays are converted using their respective Arrays.toString 
methods while
+     * special handling is implemented for "container types", i.e. Object[], 
Map and Collection because those could
+     * contain themselves.
+     * <p>
+     * dejaVu is used in case of those container types to prevent an endless 
recursion.
+     * </p>
+     * <p>
+     * It should be noted that neither AbstractMap.toString() nor 
AbstractCollection.toString() implement such a
+     * behavior.
+     * They only check if the container is directly contained in itself, but 
not if a contained container contains the
+     * original one. Because of that, Arrays.toString(Object[]) isn't safe 
either.
+     * Confusing? Just read the last paragraph again and check the respective 
toString() implementation.
+     * </p>
+     * <p>
+     * This means, in effect, that logging would produce a usable output even 
if an ordinary System.out.println(o)
+     * would produce a relatively hard-to-debug StackOverflowError.
+     * </p>
+     *
+     * @param o      the Object to convert into a String
+     * @param str    the StringBuilder that o will be appended to
+     * @param dejaVu a list of container identities that were already used.
+     */
+    private static void recursiveDeepToString(final Object o, final 
StringBuilder str, final Set<String> dejaVu) {
+        if (appendStringDateOrNull(o, str)) {
+            return;
+        }
+        if (isMaybeRecursive(o)) {
+            appendPotentiallyRecursiveValue(o, str, dejaVu);
+        } else {
+            tryObjectToString(o, str);
+        }
+    }
+
+    private static boolean appendStringDateOrNull(final Object o, final 
StringBuilder str) {
+        if (o == null || o instanceof String) {
+            str.append(String.valueOf(o));
+            return true;
+        }
+        return appendDate(o, str);
+    }
+
+    private static boolean appendDate(final Object o, final StringBuilder str) 
{
+        if (!(o instanceof Date)) {
+            return false;
+        }
+        final Date date = (Date) o;
+        final SimpleDateFormat format = getSimpleDateFormat();
+        str.append(format.format(date));
+        return true;
+    }
+
+    private static SimpleDateFormat getSimpleDateFormat() {
+        // I'll leave it like this for the moment... this could probably be 
optimized using ThreadLocal...
+        return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+    }
+
+    /**
+     * Returns {@code true} if the specified object is an array, a Map or a 
Collection.
+     */
+    private static boolean isMaybeRecursive(final Object o) {
+        return o.getClass().isArray() || o instanceof Map || o instanceof 
Collection;
+    }
+
+    private static void appendPotentiallyRecursiveValue(final Object o, final 
StringBuilder str,
+            final Set<String> dejaVu) {
+        final Class<?> oClass = o.getClass();
+        if (oClass.isArray()) {
+            appendArray(o, str, dejaVu, oClass);
+        } else if (o instanceof Map) {
+            appendMap(o, str, dejaVu);
+        } else if (o instanceof Collection) {
+            appendCollection(o, str, dejaVu);
+        }
+    }
+
+    private static void appendArray(final Object o, final StringBuilder str, 
final Set<String> dejaVu,
+            final Class<?> oClass) {
+        if (oClass == byte[].class) {
+            str.append(Arrays.toString((byte[]) o));
+        } else if (oClass == short[].class) {
+            str.append(Arrays.toString((short[]) o));
+        } else if (oClass == int[].class) {
+            str.append(Arrays.toString((int[]) o));
+        } else if (oClass == long[].class) {
+            str.append(Arrays.toString((long[]) o));
+        } else if (oClass == float[].class) {
+            str.append(Arrays.toString((float[]) o));
+        } else if (oClass == double[].class) {
+            str.append(Arrays.toString((double[]) o));
+        } else if (oClass == boolean[].class) {
+            str.append(Arrays.toString((boolean[]) o));
+        } else if (oClass == char[].class) {
+            str.append(Arrays.toString((char[]) o));
+        } else {
+            // special handling of container Object[]
+            final String id = identityToString(o);
+            if (dejaVu.contains(id)) {
+                
str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
+            } else {
+                dejaVu.add(id);
+                final Object[] oArray = (Object[]) o;
+                str.append('[');
+                boolean first = true;
+                for (final Object current : oArray) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        str.append(", ");
+                    }
+                    recursiveDeepToString(current, str, new HashSet<>(dejaVu));
+                }
+                str.append(']');
+            }
+            //str.append(Arrays.deepToString((Object[]) o));
+        }
+    }
+
+    private static void appendMap(final Object o, final StringBuilder str, 
final Set<String> dejaVu) {
+        // special handling of container Map
+        final String id = identityToString(o);
+        if (dejaVu.contains(id)) {
+            str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
+        } else {
+            dejaVu.add(id);
+            final Map<?, ?> oMap = (Map<?, ?>) o;
+            str.append('{');
+            boolean isFirst = true;
+            for (final Object o1 : oMap.entrySet()) {
+                final Map.Entry<?, ?> current = (Map.Entry<?, ?>) o1;
+                if (isFirst) {
+                    isFirst = false;
+                } else {
+                    str.append(", ");
+                }
+                final Object key = current.getKey();
+                final Object value = current.getValue();
+                recursiveDeepToString(key, str, new HashSet<>(dejaVu));
+                str.append('=');
+                recursiveDeepToString(value, str, new HashSet<>(dejaVu));
+            }
+            str.append('}');
+        }
+    }
+
+    private static void appendCollection(final Object o, final StringBuilder 
str, final Set<String> dejaVu) {
+        // special handling of container Collection
+        final String id = identityToString(o);
+        if (dejaVu.contains(id)) {
+            str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX);
+        } else {
+            dejaVu.add(id);
+            final Collection<?> oCol = (Collection<?>) o;
+            str.append('[');
+            boolean isFirst = true;
+            for (final Object anOCol : oCol) {
+                if (isFirst) {
+                    isFirst = false;
+                } else {
+                    str.append(", ");
+                }
+                recursiveDeepToString(anOCol, str, new HashSet<>(dejaVu));
+            }
+            str.append(']');
+        }
+    }
+
+    private static void tryObjectToString(final Object o, final StringBuilder 
str) {
+        // it's just some other Object, we can only use toString().
+        try {
+            str.append(o.toString());
+        } catch (final Throwable t) {
+            handleErrorInObjectToString(o, str, t);
+        }
+    }
+
+    private static void handleErrorInObjectToString(final Object o, final 
StringBuilder str, final Throwable t) {
+        str.append(ERROR_PREFIX);
+        str.append(identityToString(o));
+        str.append(ERROR_SEPARATOR);
+        final String msg = t.getMessage();
+        final String className = t.getClass().getName();
+        str.append(className);
+        if (!className.equals(msg)) {
+            str.append(ERROR_MSG_SEPARATOR);
+            str.append(msg);
+        }
+        str.append(ERROR_SUFFIX);
+    }
+
+    /**
+     * This method returns the same as if Object.toString() would not have been
+     * overridden in obj.
+     * <p>
+     * Note that this isn't 100% secure as collisions can always happen with 
hash codes.
+     * </p>
+     * <p>
+     * Copied from Object.hashCode():
+     * </p>
+     * <blockquote>
+     * As much as is reasonably practical, the hashCode method defined by
+     * class {@code Object} does return distinct integers for distinct
+     * objects. (This is typically implemented by converting the internal
+     * address of the object into an integer, but this implementation
+     * technique is not required by the Java&#8482; programming language.)
+     * </blockquote>
+     *
+     * @param obj the Object that is to be converted into an identity string.
+     * @return the identity string as also defined in Object.toString()
+     */
+    public static String identityToString(final Object obj) {
+        if (obj == null) {
+            return null;
+        }
+        return obj.getClass().getName() + '@' + 
Integer.toHexString(System.identityHashCode(obj));
+    }
+
+    @Override
+    public String toString() {
+        return getFormattedMessage();
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/dca586cf/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 06d19a6..f9ea553 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -24,6 +24,9 @@
   </properties>
   <body>
     <release version="2.6" date="2016-MM-DD" description="GA Release 2.6">
+      <action issue="LOG4J2-1271" dev="rpopma" type="update">
+        ParameterizedMessage optimizations to avoid or at least postpone 
allocating temporary objects.
+      </action>
       <action issue="LOG4J2-1283" dev="rpopma" type="update">
         Provide ThreadLocal-based gc-free caching mechanism in 
DatePatternConverter for non-webapps.
       </action>

Reply via email to