blob: bb59d468c2d3d947e7c90c3c4ca50400430b63e6 [file] [log] [blame]
/*
* 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.util;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.status.StatusLogger;
/**
* Utility for preventing primitive parameter values from being auto-boxed. Auto-boxing creates temporary objects
* which contribute to pressure on the garbage collector. With this utility users can convert primitive values directly
* into text without allocating temporary objects.
* <p>
* Example usage:
* </p><pre>
* import static org.apache.logging.log4j.util.Unbox.box;
* ...
* long longValue = 123456L;
* double doubleValue = 3.14;
* // prevent primitive values from being auto-boxed
* logger.debug("Long value={}, double value={}", box(longValue), box(doubleValue));
* </pre>
* <p>
* This class manages a small thread-local ring buffer of StringBuilders.
* Each time one of the {@code box()} methods is called, the next slot in the ring buffer is used, until the ring
* buffer is full and the first slot is reused. By default the Unbox ring buffer has 32 slots, so user code can
* have up to 32 boxed primitives in a single logger call.
* </p>
* <p>
* If more slots are required, set system property {@code log4j.unbox.ringbuffer.size} to the desired ring buffer size.
* Note that the specified number will be rounded up to the nearest power of 2.
* </p>
* @since 2.6
*/
@PerformanceSensitive("allocation")
public class Unbox {
private static final Logger LOGGER = StatusLogger.getLogger();
private static final int BITS_PER_INT = 32;
private static final int RINGBUFFER_MIN_SIZE = 32;
private static final int RINGBUFFER_SIZE = calculateRingBufferSize("log4j.unbox.ringbuffer.size");
private static final int MASK = RINGBUFFER_SIZE - 1;
/**
* State implementation that only puts JDK classes in ThreadLocals, so this is safe to be used from
* web applications. Web application containers have thread pools that may hold on to ThreadLocal objects
* after the application was stopped. This may prevent the classes of the application from being unloaded,
* causing memory leaks.
* <p>
* Such memory leaks will not occur if only JDK classes are stored in ThreadLocals.
* </p>
*/
private static class WebSafeState {
private final ThreadLocal<StringBuilder[]> ringBuffer = new ThreadLocal<>();
private final ThreadLocal<int[]> current = new ThreadLocal<>();
public StringBuilder getStringBuilder() {
StringBuilder[] array = ringBuffer.get();
if (array == null) {
array = new StringBuilder[RINGBUFFER_SIZE];
for (int i = 0; i < array.length; i++) {
array[i] = new StringBuilder(21);
}
ringBuffer.set(array);
current.set(new int[1]);
}
final int[] index = current.get();
final StringBuilder result = array[MASK & index[0]++];
result.setLength(0);
return result;
}
public boolean isBoxedPrimitive(final StringBuilder text) {
final StringBuilder[] array = ringBuffer.get();
if (array == null) {
return false;
}
for (int i = 0; i < array.length; i++) {
if (text == array[i]) {
return true;
}
}
return false;
}
}
private static class State {
private final StringBuilder[] ringBuffer = new StringBuilder[RINGBUFFER_SIZE];
private int current;
State() {
for (int i = 0; i < ringBuffer.length; i++) {
ringBuffer[i] = new StringBuilder(21);
}
}
public StringBuilder getStringBuilder() {
final StringBuilder result = ringBuffer[MASK & current++];
result.setLength(0);
return result;
}
public boolean isBoxedPrimitive(final StringBuilder text) {
for (int i = 0; i < ringBuffer.length; i++) {
if (text == ringBuffer[i]) {
return true;
}
}
return false;
}
}
private static ThreadLocal<State> threadLocalState = new ThreadLocal<>();
private static WebSafeState webSafeState = new WebSafeState();
private Unbox() {
// this is a utility
}
private static int calculateRingBufferSize(final String propertyName) {
final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName,
String.valueOf(RINGBUFFER_MIN_SIZE));
try {
int size = Integer.parseInt(userPreferredRBSize);
if (size < RINGBUFFER_MIN_SIZE) {
size = RINGBUFFER_MIN_SIZE;
LOGGER.warn("Invalid {} {}, using minimum size {}.", propertyName, userPreferredRBSize,
RINGBUFFER_MIN_SIZE);
}
return ceilingNextPowerOfTwo(size);
} catch (final Exception ex) {
LOGGER.warn("Invalid {} {}, using default size {}.", propertyName, userPreferredRBSize,
RINGBUFFER_MIN_SIZE);
return RINGBUFFER_MIN_SIZE;
}
}
/**
* Calculate the next power of 2, greater than or equal to x.
* <p>
* From Hacker's Delight, Chapter 3, Harry S. Warren Jr.
*
* @param x Value to round up
* @return The next power of 2 from x inclusive
*/
private static int ceilingNextPowerOfTwo(final int x) {
return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1));
}
/**
* Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
* This method will not allocate temporary objects.
*
* @param value the value whose text representation to return
* @return a {@code StringBuilder} containing the text representation of the specified primitive value
*/
@PerformanceSensitive("allocation")
public static StringBuilder box(final float value) {
return getSB().append(value);
}
/**
* Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
* This method will not allocate temporary objects.
*
* @param value the value whose text representation to return
* @return a {@code StringBuilder} containing the text representation of the specified primitive value
*/
@PerformanceSensitive("allocation")
public static StringBuilder box(final double value) {
return getSB().append(value);
}
/**
* Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
* This method will not allocate temporary objects.
*
* @param value the value whose text representation to return
* @return a {@code StringBuilder} containing the text representation of the specified primitive value
*/
@PerformanceSensitive("allocation")
public static StringBuilder box(final short value) {
return getSB().append(value);
}
/**
* Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
* This method will not allocate temporary objects.
*
* @param value the value whose text representation to return
* @return a {@code StringBuilder} containing the text representation of the specified primitive value
*/
@PerformanceSensitive("allocation")
public static StringBuilder box(final int value) {
return getSB().append(value);
}
/**
* Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
* This method will not allocate temporary objects.
*
* @param value the value whose text representation to return
* @return a {@code StringBuilder} containing the text representation of the specified primitive value
*/
@PerformanceSensitive("allocation")
public static StringBuilder box(final char value) {
return getSB().append(value);
}
/**
* Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
* This method will not allocate temporary objects.
*
* @param value the value whose text representation to return
* @return a {@code StringBuilder} containing the text representation of the specified primitive value
*/
@PerformanceSensitive("allocation")
public static StringBuilder box(final long value) {
return getSB().append(value);
}
/**
* Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
* This method will not allocate temporary objects.
*
* @param value the value whose text representation to return
* @return a {@code StringBuilder} containing the text representation of the specified primitive value
*/
@PerformanceSensitive("allocation")
public static StringBuilder box(final byte value) {
return getSB().append(value);
}
/**
* Returns a {@code StringBuilder} containing the text representation of the specified primitive value.
* This method will not allocate temporary objects.
*
* @param value the value whose text representation to return
* @return a {@code StringBuilder} containing the text representation of the specified primitive value
*/
@PerformanceSensitive("allocation")
public static StringBuilder box(final boolean value) {
return getSB().append(value);
}
private static State getState() {
State state = threadLocalState.get();
if (state == null) {
state = new State();
threadLocalState.set(state);
}
return state;
}
private static StringBuilder getSB() {
return Constants.ENABLE_THREADLOCALS ? getState().getStringBuilder() : webSafeState.getStringBuilder();
}
/** For testing. */
static int getRingbufferSize() {
return RINGBUFFER_SIZE;
}
}