1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache license, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the license for the specific language governing permissions and 15 * limitations under the license. 16 */ 17 package org.apache.logging.log4j.util; 18 19 import org.apache.logging.log4j.Logger; 20 import org.apache.logging.log4j.status.StatusLogger; 21 22 /** 23 * Utility for preventing primitive parameter values from being auto-boxed. Auto-boxing creates temporary objects 24 * which contribute to pressure on the garbage collector. With this utility users can convert primitive values directly 25 * into text without allocating temporary objects. 26 * <p> 27 * Example usage: 28 * </p><pre> 29 * import static org.apache.logging.log4j.util.Unbox.box; 30 * ... 31 * long longValue = 123456L; 32 * double doubleValue = 3.14; 33 * // prevent primitive values from being auto-boxed 34 * logger.debug("Long value={}, double value={}", box(longValue), box(doubleValue)); 35 * </pre> 36 * <p> 37 * This class manages a small thread-local ring buffer of StringBuilders. 38 * Each time one of the {@code box()} methods is called, the next slot in the ring buffer is used, until the ring 39 * buffer is full and the first slot is reused. By default the Unbox ring buffer has 32 slots, so user code can 40 * have up to 32 boxed primitives in a single logger call. 41 * </p> 42 * <p> 43 * If more slots are required, set system property {@code log4j.unbox.ringbuffer.size} to the desired ring buffer size. 44 * Note that the specified number will be rounded up to the nearest power of 2. 45 * </p> 46 * @since 2.6 47 */ 48 @PerformanceSensitive("allocation") 49 public class Unbox { 50 private static final Logger LOGGER = StatusLogger.getLogger(); 51 private static final int BITS_PER_INT = 32; 52 private static final int RINGBUFFER_MIN_SIZE = 32; 53 private static final int RINGBUFFER_SIZE = calculateRingBufferSize("log4j.unbox.ringbuffer.size"); 54 private static final int MASK = RINGBUFFER_SIZE - 1; 55 56 /** 57 * State implementation that only puts JDK classes in ThreadLocals, so this is safe to be used from 58 * web applications. Web application containers have thread pools that may hold on to ThreadLocal objects 59 * after the application was stopped. This may prevent the classes of the application from being unloaded, 60 * causing memory leaks. 61 * <p> 62 * Such memory leaks will not occur if only JDK classes are stored in ThreadLocals. 63 * </p> 64 */ 65 private static class WebSafeState { 66 private final ThreadLocal<StringBuilder[]> ringBuffer = new ThreadLocal<>(); 67 private final ThreadLocal<int[]> current = new ThreadLocal<>(); 68 69 public StringBuilder getStringBuilder() { 70 StringBuilder[] array = ringBuffer.get(); 71 if (array == null) { 72 array = new StringBuilder[RINGBUFFER_SIZE]; 73 for (int i = 0; i < array.length; i++) { 74 array[i] = new StringBuilder(21); 75 } 76 ringBuffer.set(array); 77 current.set(new int[1]); 78 } 79 final int[] index = current.get(); 80 final StringBuilder result = array[MASK & index[0]++]; 81 result.setLength(0); 82 return result; 83 } 84 85 public boolean isBoxedPrimitive(final StringBuilder text) { 86 final StringBuilder[] array = ringBuffer.get(); 87 if (array == null) { 88 return false; 89 } 90 for (int i = 0; i < array.length; i++) { 91 if (text == array[i]) { 92 return true; 93 } 94 } 95 return false; 96 } 97 } 98 99 private static class State { 100 private final StringBuilder[] ringBuffer = new StringBuilder[RINGBUFFER_SIZE]; 101 private int current; 102 State() { 103 for (int i = 0; i < ringBuffer.length; i++) { 104 ringBuffer[i] = new StringBuilder(21); 105 } 106 } 107 108 public StringBuilder getStringBuilder() { 109 final StringBuilder result = ringBuffer[MASK & current++]; 110 result.setLength(0); 111 return result; 112 } 113 114 public boolean isBoxedPrimitive(final StringBuilder text) { 115 for (int i = 0; i < ringBuffer.length; i++) { 116 if (text == ringBuffer[i]) { 117 return true; 118 } 119 } 120 return false; 121 } 122 } 123 private static ThreadLocal<State> threadLocalState = new ThreadLocal<>(); 124 private static WebSafeState webSafeState = new WebSafeState(); 125 126 private Unbox() { 127 // this is a utility 128 } 129 130 private static int calculateRingBufferSize(final String propertyName) { 131 final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName, 132 String.valueOf(RINGBUFFER_MIN_SIZE)); 133 try { 134 int size = Integer.parseInt(userPreferredRBSize); 135 if (size < RINGBUFFER_MIN_SIZE) { 136 size = RINGBUFFER_MIN_SIZE; 137 LOGGER.warn("Invalid {} {}, using minimum size {}.", propertyName, userPreferredRBSize, 138 RINGBUFFER_MIN_SIZE); 139 } 140 return ceilingNextPowerOfTwo(size); 141 } catch (final Exception ex) { 142 LOGGER.warn("Invalid {} {}, using default size {}.", propertyName, userPreferredRBSize, 143 RINGBUFFER_MIN_SIZE); 144 return RINGBUFFER_MIN_SIZE; 145 } 146 } 147 148 /** 149 * Calculate the next power of 2, greater than or equal to x. 150 * <p> 151 * From Hacker's Delight, Chapter 3, Harry S. Warren Jr. 152 * 153 * @param x Value to round up 154 * @return The next power of 2 from x inclusive 155 */ 156 private static int ceilingNextPowerOfTwo(final int x) { 157 return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1)); 158 } 159 160 /** 161 * Returns a {@code StringBuilder} containing the text representation of the specified primitive value. 162 * This method will not allocate temporary objects. 163 * 164 * @param value the value whose text representation to return 165 * @return a {@code StringBuilder} containing the text representation of the specified primitive value 166 */ 167 @PerformanceSensitive("allocation") 168 public static StringBuilder box(final float value) { 169 return getSB().append(value); 170 } 171 172 /** 173 * Returns a {@code StringBuilder} containing the text representation of the specified primitive value. 174 * This method will not allocate temporary objects. 175 * 176 * @param value the value whose text representation to return 177 * @return a {@code StringBuilder} containing the text representation of the specified primitive value 178 */ 179 @PerformanceSensitive("allocation") 180 public static StringBuilder box(final double value) { 181 return getSB().append(value); 182 } 183 184 /** 185 * Returns a {@code StringBuilder} containing the text representation of the specified primitive value. 186 * This method will not allocate temporary objects. 187 * 188 * @param value the value whose text representation to return 189 * @return a {@code StringBuilder} containing the text representation of the specified primitive value 190 */ 191 @PerformanceSensitive("allocation") 192 public static StringBuilder box(final short value) { 193 return getSB().append(value); 194 } 195 196 /** 197 * Returns a {@code StringBuilder} containing the text representation of the specified primitive value. 198 * This method will not allocate temporary objects. 199 * 200 * @param value the value whose text representation to return 201 * @return a {@code StringBuilder} containing the text representation of the specified primitive value 202 */ 203 @PerformanceSensitive("allocation") 204 public static StringBuilder box(final int value) { 205 return getSB().append(value); 206 } 207 208 /** 209 * Returns a {@code StringBuilder} containing the text representation of the specified primitive value. 210 * This method will not allocate temporary objects. 211 * 212 * @param value the value whose text representation to return 213 * @return a {@code StringBuilder} containing the text representation of the specified primitive value 214 */ 215 @PerformanceSensitive("allocation") 216 public static StringBuilder box(final char value) { 217 return getSB().append(value); 218 } 219 220 /** 221 * Returns a {@code StringBuilder} containing the text representation of the specified primitive value. 222 * This method will not allocate temporary objects. 223 * 224 * @param value the value whose text representation to return 225 * @return a {@code StringBuilder} containing the text representation of the specified primitive value 226 */ 227 @PerformanceSensitive("allocation") 228 public static StringBuilder box(final long value) { 229 return getSB().append(value); 230 } 231 232 /** 233 * Returns a {@code StringBuilder} containing the text representation of the specified primitive value. 234 * This method will not allocate temporary objects. 235 * 236 * @param value the value whose text representation to return 237 * @return a {@code StringBuilder} containing the text representation of the specified primitive value 238 */ 239 @PerformanceSensitive("allocation") 240 public static StringBuilder box(final byte value) { 241 return getSB().append(value); 242 } 243 244 /** 245 * Returns a {@code StringBuilder} containing the text representation of the specified primitive value. 246 * This method will not allocate temporary objects. 247 * 248 * @param value the value whose text representation to return 249 * @return a {@code StringBuilder} containing the text representation of the specified primitive value 250 */ 251 @PerformanceSensitive("allocation") 252 public static StringBuilder box(final boolean value) { 253 return getSB().append(value); 254 } 255 256 private static State getState() { 257 State state = threadLocalState.get(); 258 if (state == null) { 259 state = new State(); 260 threadLocalState.set(state); 261 } 262 return state; 263 } 264 265 private static StringBuilder getSB() { 266 return Constants.ENABLE_THREADLOCALS ? getState().getStringBuilder() : webSafeState.getStringBuilder(); 267 } 268 269 /** For testing. */ 270 static int getRingbufferSize() { 271 return RINGBUFFER_SIZE; 272 } 273 }