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