View Javadoc

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