View Javadoc
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 }