View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.util;
22  
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Modifier;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.ConcurrentSkipListMap;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  
32  /**
33   * Class for determining the "size" of a class, an attempt to calculate the
34   * actual bytes that an object of this class will occupy in memory
35   *
36   * The core of this class is taken from the Derby project
37   */
38  @InterfaceAudience.Private
39  public class ClassSize {
40    private static final Log LOG = LogFactory.getLog(ClassSize.class);
41  
42    private static int nrOfRefsPerObj = 2;
43  
44    /** Array overhead */
45    public static final int ARRAY;
46  
47    /** Overhead for ArrayList(0) */
48    public static final int ARRAYLIST;
49  
50    /** Overhead for ByteBuffer */
51    public static final int BYTE_BUFFER;
52  
53    /** Overhead for an Integer */
54    public static final int INTEGER;
55  
56    /** Overhead for entry in map */
57    public static final int MAP_ENTRY;
58  
59    /** Object overhead is minimum 2 * reference size (8 bytes on 64-bit) */
60    public static final int OBJECT;
61  
62    /** Reference size is 8 bytes on 64-bit, 4 bytes on 32-bit */
63    public static final int REFERENCE;
64  
65    /** String overhead */
66    public static final int STRING;
67  
68    /** Overhead for TreeMap */
69    public static final int TREEMAP;
70  
71    /** Overhead for ConcurrentHashMap */
72    public static final int CONCURRENT_HASHMAP;
73  
74    /** Overhead for ConcurrentHashMap.Entry */
75    public static final int CONCURRENT_HASHMAP_ENTRY;
76  
77    /** Overhead for ConcurrentHashMap.Segment */
78    public static final int CONCURRENT_HASHMAP_SEGMENT;
79  
80    /** Overhead for ConcurrentSkipListMap */
81    public static final int CONCURRENT_SKIPLISTMAP;
82  
83    /** Overhead for ConcurrentSkipListMap Entry */
84    public static final int CONCURRENT_SKIPLISTMAP_ENTRY;
85  
86    /** Overhead for ReentrantReadWriteLock */
87    public static final int REENTRANT_LOCK;
88  
89    /** Overhead for AtomicLong */
90    public static final int ATOMIC_LONG;
91  
92    /** Overhead for AtomicInteger */
93    public static final int ATOMIC_INTEGER;
94  
95    /** Overhead for AtomicBoolean */
96    public static final int ATOMIC_BOOLEAN;
97  
98    /** Overhead for CopyOnWriteArraySet */
99    public static final int COPYONWRITE_ARRAYSET;
100 
101   /** Overhead for CopyOnWriteArrayList */
102   public static final int COPYONWRITE_ARRAYLIST;
103 
104   /** Overhead for timerange */
105   public static final int TIMERANGE;
106 
107   /** Overhead for TimeRangeTracker */
108   public static final int TIMERANGE_TRACKER;
109 
110   /** Overhead for CellSkipListSet */
111   public static final int CELL_SKIPLIST_SET;
112 
113   public static final int STORE_SERVICES;
114 
115   /* Are we running on jdk7? */
116   private static final boolean JDK7;
117   static {
118     final String version = System.getProperty("java.version");
119     // Verify String looks like this: 1.6.0_29
120     if (version == null || !version.matches("\\d\\.\\d\\..*")) {
121       throw new RuntimeException("Unexpected version format: " + version);
122     }
123     // Convert char to int
124     int major = (int)(version.charAt(0) - '0');
125     int minor = (int)(version.charAt(2) - '0');
126     JDK7 = major == 1 && minor == 7;
127   }
128 
129   /**
130    * Method for reading the arc settings and setting overheads according
131    * to 32-bit or 64-bit architecture.
132    */
133   static {
134     //Default value is set to 8, covering the case when arcModel is unknown
135     if (is32BitJVM()) {
136       REFERENCE = 4;
137     } else {
138       REFERENCE = 8;
139     }
140 
141     OBJECT = 2 * REFERENCE;
142 
143     ARRAY = align(3 * REFERENCE);
144 
145     ARRAYLIST = align(OBJECT + align(REFERENCE) + align(ARRAY) +
146         (2 * Bytes.SIZEOF_INT));
147 
148     //noinspection PointlessArithmeticExpression
149     BYTE_BUFFER = align(OBJECT + align(REFERENCE) + align(ARRAY) +
150         (5 * Bytes.SIZEOF_INT) +
151         (3 * Bytes.SIZEOF_BOOLEAN) + Bytes.SIZEOF_LONG);
152 
153     INTEGER = align(OBJECT + Bytes.SIZEOF_INT);
154 
155     MAP_ENTRY = align(OBJECT + 5 * REFERENCE + Bytes.SIZEOF_BOOLEAN);
156 
157     TREEMAP = align(OBJECT + (2 * Bytes.SIZEOF_INT) + align(7 * REFERENCE));
158 
159     // STRING is different size in jdk6 and jdk7. Just use what we estimate as size rather than
160     // have a conditional on whether jdk7.
161     STRING = (int) estimateBase(String.class, false);
162 
163     // CONCURRENT_HASHMAP is different size in jdk6 and jdk7; it looks like its different between
164     // 23.6-b03 and 23.0-b21. Just use what we estimate as size rather than have a conditional on
165     // whether jdk7.
166     CONCURRENT_HASHMAP = (int) estimateBase(ConcurrentHashMap.class, false);
167 
168     CONCURRENT_HASHMAP_ENTRY = align(REFERENCE + OBJECT + (3 * REFERENCE) +
169         (2 * Bytes.SIZEOF_INT));
170 
171     CONCURRENT_HASHMAP_SEGMENT = align(REFERENCE + OBJECT +
172         (3 * Bytes.SIZEOF_INT) + Bytes.SIZEOF_FLOAT + ARRAY);
173 
174     // The size changes from jdk7 to jdk8, estimate the size rather than use a conditional
175     CONCURRENT_SKIPLISTMAP = (int) estimateBase(ConcurrentSkipListMap.class, false);
176 
177     CONCURRENT_SKIPLISTMAP_ENTRY = align(
178         align(OBJECT + (3 * REFERENCE)) + /* one node per entry */
179         align((OBJECT + (3 * REFERENCE))/2)); /* one index per two entries */
180 
181     REENTRANT_LOCK = align(OBJECT + (3 * REFERENCE));
182 
183     ATOMIC_LONG = align(OBJECT + Bytes.SIZEOF_LONG);
184 
185     ATOMIC_INTEGER = align(OBJECT + Bytes.SIZEOF_INT);
186 
187     ATOMIC_BOOLEAN = align(OBJECT + Bytes.SIZEOF_BOOLEAN);
188 
189     COPYONWRITE_ARRAYSET = align(OBJECT + REFERENCE);
190 
191     COPYONWRITE_ARRAYLIST = align(OBJECT + (2 * REFERENCE) + ARRAY);
192 
193     TIMERANGE = align(ClassSize.OBJECT + Bytes.SIZEOF_LONG * 2 + Bytes.SIZEOF_BOOLEAN);
194 
195     TIMERANGE_TRACKER = align(ClassSize.OBJECT + Bytes.SIZEOF_LONG * 2);
196 
197     CELL_SKIPLIST_SET = align(OBJECT + REFERENCE);
198 
199     STORE_SERVICES = align(OBJECT + REFERENCE + ATOMIC_LONG);
200   }
201 
202   /**
203    * The estimate of the size of a class instance depends on whether the JVM
204    * uses 32 or 64 bit addresses, that is it depends on the size of an object
205    * reference. It is a linear function of the size of a reference, e.g.
206    * 24 + 5*r where r is the size of a reference (usually 4 or 8 bytes).
207    *
208    * This method returns the coefficients of the linear function, e.g. {24, 5}
209    * in the above example.
210    *
211    * @param cl A class whose instance size is to be estimated
212    * @param debug debug flag
213    * @return an array of 3 integers. The first integer is the size of the
214    * primitives, the second the number of arrays and the third the number of
215    * references.
216    */
217   @SuppressWarnings("unchecked")
218   private static int [] getSizeCoefficients(Class cl, boolean debug) {
219     int primitives = 0;
220     int arrays = 0;
221     //The number of references that a new object takes
222     int references = nrOfRefsPerObj;
223     int index = 0;
224 
225     for ( ; null != cl; cl = cl.getSuperclass()) {
226       Field[] field = cl.getDeclaredFields();
227       if (null != field) {
228         for (Field aField : field) {
229           if (Modifier.isStatic(aField.getModifiers())) continue;
230           Class fieldClass = aField.getType();
231           if (fieldClass.isArray()) {
232             arrays++;
233             references++;
234           } else if (!fieldClass.isPrimitive()) {
235             references++;
236           } else {// Is simple primitive
237             String name = fieldClass.getName();
238 
239             if (name.equals("int") || name.equals("I"))
240               primitives += Bytes.SIZEOF_INT;
241             else if (name.equals("long") || name.equals("J"))
242               primitives += Bytes.SIZEOF_LONG;
243             else if (name.equals("boolean") || name.equals("Z"))
244               primitives += Bytes.SIZEOF_BOOLEAN;
245             else if (name.equals("short") || name.equals("S"))
246               primitives += Bytes.SIZEOF_SHORT;
247             else if (name.equals("byte") || name.equals("B"))
248               primitives += Bytes.SIZEOF_BYTE;
249             else if (name.equals("char") || name.equals("C"))
250               primitives += Bytes.SIZEOF_CHAR;
251             else if (name.equals("float") || name.equals("F"))
252               primitives += Bytes.SIZEOF_FLOAT;
253             else if (name.equals("double") || name.equals("D"))
254               primitives += Bytes.SIZEOF_DOUBLE;
255           }
256           if (debug) {
257             if (LOG.isDebugEnabled()) {
258               LOG.debug("" + index + " " + aField.getName() + " " + aField.getType());
259             }
260           }
261           index++;
262         }
263       }
264     }
265     return new int [] {primitives, arrays, references};
266   }
267 
268   /**
269    * Estimate the static space taken up by a class instance given the
270    * coefficients returned by getSizeCoefficients.
271    *
272    * @param coeff the coefficients
273    *
274    * @param debug debug flag
275    * @return the size estimate, in bytes
276    */
277   private static long estimateBaseFromCoefficients(int [] coeff, boolean debug) {
278     long prealign_size = coeff[0] + align(coeff[1] * ARRAY) + coeff[2] * REFERENCE;
279 
280     // Round up to a multiple of 8
281     long size = align(prealign_size);
282     if(debug) {
283       if (LOG.isDebugEnabled()) {
284         LOG.debug("Primitives=" + coeff[0] + ", arrays=" + coeff[1] +
285             ", references(includes " + nrOfRefsPerObj +
286             " for object overhead)=" + coeff[2] + ", refSize " + REFERENCE +
287             ", size=" + size + ", prealign_size=" + prealign_size);
288       }
289     }
290     return size;
291   }
292 
293   /**
294    * Estimate the static space taken up by the fields of a class. This includes
295    * the space taken up by by references (the pointer) but not by the referenced
296    * object. So the estimated size of an array field does not depend on the size
297    * of the array. Similarly the size of an object (reference) field does not
298    * depend on the object.
299    *
300    * @param cl class
301    * @param debug debug flag
302    * @return the size estimate in bytes.
303    */
304   @SuppressWarnings("unchecked")
305   public static long estimateBase(Class cl, boolean debug) {
306     return estimateBaseFromCoefficients( getSizeCoefficients(cl, debug), debug);
307   }
308 
309   /**
310    * Aligns a number to 8.
311    * @param num number to align to 8
312    * @return smallest number >= input that is a multiple of 8
313    */
314   public static int align(int num) {
315     return (int)(align((long)num));
316   }
317 
318   /**
319    * Aligns a number to 8.
320    * @param num number to align to 8
321    * @return smallest number >= input that is a multiple of 8
322    */
323   public static long align(long num) {
324     //The 7 comes from that the alignSize is 8 which is the number of bytes
325     //stored and sent together
326     return  ((num + 7) >> 3) << 3;
327   }
328 
329   /**
330    * Determines if we are running in a 32-bit JVM. Some unit tests need to
331    * know this too.
332    */
333   public static boolean is32BitJVM() {
334     final String model = System.getProperty("sun.arch.data.model");
335     return model != null && model.equals("32");
336   }
337 
338 }
339