001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.util; 018 019import org.apache.logging.log4j.Logger; 020import org.apache.logging.log4j.status.StatusLogger; 021 022/** 023 * Utility for preventing primitive parameter values from being auto-boxed. Auto-boxing creates temporary objects 024 * which contribute to pressure on the garbage collector. With this utility users can convert primitive values directly 025 * into text without allocating temporary objects. 026 * <p> 027 * Example usage: 028 * </p><pre> 029 * import static org.apache.logging.log4j.util.Unbox.box; 030 * ... 031 * long longValue = 123456L; 032 * double doubleValue = 3.14; 033 * // prevent primitive values from being auto-boxed 034 * logger.debug("Long value={}, double value={}", box(longValue), box(doubleValue)); 035 * </pre> 036 * <p> 037 * This class manages a small thread-local ring buffer of StringBuilders. 038 * Each time one of the {@code box()} methods is called, the next slot in the ring buffer is used, until the ring 039 * buffer is full and the first slot is reused. By default the Unbox ring buffer has 32 slots, so user code can 040 * have up to 32 boxed primitives in a single logger call. 041 * </p> 042 * <p> 043 * If more slots are required, set system property {@code log4j.unbox.ringbuffer.size} to the desired ring buffer size. 044 * Note that the specified number will be rounded up to the nearest power of 2. 045 * </p> 046 * @since 2.6 047 */ 048@PerformanceSensitive("allocation") 049public class Unbox { 050 private static final Logger LOGGER = StatusLogger.getLogger(); 051 private static final int BITS_PER_INT = 32; 052 private static final int RINGBUFFER_MIN_SIZE = 32; 053 private static final int RINGBUFFER_SIZE = calculateRingBufferSize("log4j.unbox.ringbuffer.size"); 054 private static final int MASK = RINGBUFFER_SIZE - 1; 055 056 /** 057 * State implementation that only puts JDK classes in ThreadLocals, so this is safe to be used from 058 * web applications. Web application containers have thread pools that may hold on to ThreadLocal objects 059 * after the application was stopped. This may prevent the classes of the application from being unloaded, 060 * causing memory leaks. 061 * <p> 062 * Such memory leaks will not occur if only JDK classes are stored in ThreadLocals. 063 * </p> 064 */ 065 private static class WebSafeState { 066 private final ThreadLocal<StringBuilder[]> ringBuffer = new ThreadLocal<>(); 067 private final ThreadLocal<int[]> current = new ThreadLocal<>(); 068 069 public StringBuilder getStringBuilder() { 070 StringBuilder[] array = ringBuffer.get(); 071 if (array == null) { 072 array = new StringBuilder[RINGBUFFER_SIZE]; 073 for (int i = 0; i < array.length; i++) { 074 array[i] = new StringBuilder(21); 075 } 076 ringBuffer.set(array); 077 current.set(new int[1]); 078 } 079 final int[] index = current.get(); 080 final StringBuilder result = array[MASK & index[0]++]; 081 result.setLength(0); 082 return result; 083 } 084 085 public boolean isBoxedPrimitive(final StringBuilder text) { 086 final StringBuilder[] array = ringBuffer.get(); 087 if (array == null) { 088 return false; 089 } 090 for (int i = 0; i < array.length; i++) { 091 if (text == array[i]) { 092 return true; 093 } 094 } 095 return false; 096 } 097 } 098 099 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}