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 }