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   /* Are we running on jdk7? */
103   private static final boolean JDK7;
104   static {
105     final String version = System.getProperty("java.version");
106     // Verify String looks like this: 1.6.0_29
107     if (!version.matches("\\d\\.\\d\\..*")) {
108       throw new RuntimeException("Unexpected version format: " + version);
109     }
110     // Convert char to int
111     int major = (int) (version.charAt(0) - '0');
112     int minor = (int) (version.charAt(2) - '0');
113     JDK7 = major == 1 && minor == 7;
114   }
115 
116   /**
117    * Method for reading the arc settings and setting overheads according
118    * to 32-bit or 64-bit architecture.
119    */
120   static {
121     //Default value is set to 8, covering the case when arcModel is unknown
122     if (is32BitJVM()) {
123       REFERENCE = 4;
124     } else {
125       REFERENCE = 8;
126     }
127 
128     OBJECT = 2 * REFERENCE;
129 
130     ARRAY = align(3 * REFERENCE);
131 
132     ARRAYLIST = align(OBJECT + align(REFERENCE) + align(ARRAY) +
133         (2 * Bytes.SIZEOF_INT));
134 
135     //noinspection PointlessArithmeticExpression
136     BYTE_BUFFER = align(OBJECT + align(REFERENCE) + align(ARRAY) +
137         (5 * Bytes.SIZEOF_INT) +
138         (3 * Bytes.SIZEOF_BOOLEAN) + Bytes.SIZEOF_LONG);
139 
140     INTEGER = align(OBJECT + Bytes.SIZEOF_INT);
141 
142     MAP_ENTRY = align(OBJECT + 5 * REFERENCE + Bytes.SIZEOF_BOOLEAN);
143 
144     TREEMAP = align(OBJECT + (2 * Bytes.SIZEOF_INT) + align(7 * REFERENCE));
145 
146     // STRING is different size in jdk6 and jdk7. Just use what we estimate as size rather than
147     // have a conditional on whether jdk7.
148     STRING = (int) estimateBase(String.class, false);
149 
150     // CONCURRENT_HASHMAP is different size in jdk6 and jdk7; it looks like its different between
151     // 23.6-b03 and 23.0-b21. Just use what we estimate as size rather than have a conditional on
152     // whether jdk7.
153     CONCURRENT_HASHMAP = (int) estimateBase(ConcurrentHashMap.class, false);
154 
155     CONCURRENT_HASHMAP_ENTRY = align(REFERENCE + OBJECT + (3 * REFERENCE) +
156         (2 * Bytes.SIZEOF_INT));
157 
158     CONCURRENT_HASHMAP_SEGMENT = align(REFERENCE + OBJECT +
159         (3 * Bytes.SIZEOF_INT) + Bytes.SIZEOF_FLOAT + ARRAY);
160 
161     CONCURRENT_SKIPLISTMAP = align(Bytes.SIZEOF_INT + OBJECT + (8 * REFERENCE));
162 
163     CONCURRENT_SKIPLISTMAP_ENTRY = align(
164         align(OBJECT + (3 * REFERENCE)) + /* one node per entry */
165         align((OBJECT + (3 * REFERENCE))/2)); /* one index per two entries */
166 
167     REENTRANT_LOCK = align(OBJECT + (3 * REFERENCE));
168 
169     ATOMIC_LONG = align(OBJECT + Bytes.SIZEOF_LONG);
170 
171     ATOMIC_INTEGER = align(OBJECT + Bytes.SIZEOF_INT);
172 
173     ATOMIC_BOOLEAN = align(OBJECT + Bytes.SIZEOF_BOOLEAN);
174 
175     COPYONWRITE_ARRAYSET = align(OBJECT + REFERENCE);
176 
177     COPYONWRITE_ARRAYLIST = align(OBJECT + (2 * REFERENCE) + ARRAY);
178   }
179 
180   /**
181    * The estimate of the size of a class instance depends on whether the JVM
182    * uses 32 or 64 bit addresses, that is it depends on the size of an object
183    * reference. It is a linear function of the size of a reference, e.g.
184    * 24 + 5*r where r is the size of a reference (usually 4 or 8 bytes).
185    *
186    * This method returns the coefficients of the linear function, e.g. {24, 5}
187    * in the above example.
188    *
189    * @param cl A class whose instance size is to be estimated
190    * @param debug debug flag
191    * @return an array of 3 integers. The first integer is the size of the
192    * primitives, the second the number of arrays and the third the number of
193    * references.
194    */
195   @SuppressWarnings("unchecked")
196   private static int [] getSizeCoefficients(Class cl, boolean debug) {
197     int primitives = 0;
198     int arrays = 0;
199     //The number of references that a new object takes
200     int references = nrOfRefsPerObj;
201     int index = 0;
202 
203     for ( ; null != cl; cl = cl.getSuperclass()) {
204       Field[] field = cl.getDeclaredFields();
205       if (null != field) {
206         for (Field aField : field) {
207           if (Modifier.isStatic(aField.getModifiers())) continue;
208           Class fieldClass = aField.getType();
209           if (fieldClass.isArray()) {
210             arrays++;
211             references++;
212           } else if (!fieldClass.isPrimitive()) {
213             references++;
214           } else {// Is simple primitive
215             String name = fieldClass.getName();
216 
217             if (name.equals("int") || name.equals("I"))
218               primitives += Bytes.SIZEOF_INT;
219             else if (name.equals("long") || name.equals("J"))
220               primitives += Bytes.SIZEOF_LONG;
221             else if (name.equals("boolean") || name.equals("Z"))
222               primitives += Bytes.SIZEOF_BOOLEAN;
223             else if (name.equals("short") || name.equals("S"))
224               primitives += Bytes.SIZEOF_SHORT;
225             else if (name.equals("byte") || name.equals("B"))
226               primitives += Bytes.SIZEOF_BYTE;
227             else if (name.equals("char") || name.equals("C"))
228               primitives += Bytes.SIZEOF_CHAR;
229             else if (name.equals("float") || name.equals("F"))
230               primitives += Bytes.SIZEOF_FLOAT;
231             else if (name.equals("double") || name.equals("D"))
232               primitives += Bytes.SIZEOF_DOUBLE;
233           }
234           if (debug) {
235             if (LOG.isDebugEnabled()) {
236               // Write out region name as string and its encoded name.
237               LOG.debug("" + index + " " + aField.getName() + " " + aField.getType());
238             }
239           }
240           index++;
241         }
242       }
243     }
244     return new int [] {primitives, arrays, references};
245   }
246 
247   /**
248    * Estimate the static space taken up by a class instance given the
249    * coefficients returned by getSizeCoefficients.
250    *
251    * @param coeff the coefficients
252    *
253    * @param debug debug flag
254    * @return the size estimate, in bytes
255    */
256   private static long estimateBaseFromCoefficients(int [] coeff, boolean debug) {
257     long prealign_size = coeff[0] + align(coeff[1] * ARRAY) + coeff[2] * REFERENCE;
258 
259     // Round up to a multiple of 8
260     long size = align(prealign_size);
261     if(debug) {
262       if (LOG.isDebugEnabled()) {
263         // Write out region name as string and its encoded name.
264         LOG.debug("Primitives=" + coeff[0] + ", arrays=" + coeff[1] +
265             ", references(includes " + nrOfRefsPerObj +
266             " for object overhead)=" + coeff[2] + ", refSize " + REFERENCE +
267             ", size=" + size + ", prealign_size=" + prealign_size);
268       }
269     }
270     return size;
271   }
272 
273   /**
274    * Estimate the static space taken up by the fields of a class. This includes
275    * the space taken up by by references (the pointer) but not by the referenced
276    * object. So the estimated size of an array field does not depend on the size
277    * of the array. Similarly the size of an object (reference) field does not
278    * depend on the object.
279    *
280    * @param cl class
281    * @param debug debug flag
282    * @return the size estimate in bytes.
283    */
284   @SuppressWarnings("unchecked")
285   public static long estimateBase(Class cl, boolean debug) {
286     return estimateBaseFromCoefficients( getSizeCoefficients(cl, debug), debug);
287   }
288 
289   /**
290    * Aligns a number to 8.
291    * @param num number to align to 8
292    * @return smallest number >= input that is a multiple of 8
293    */
294   public static int align(int num) {
295     return (int)(align((long)num));
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 long align(long num) {
304     //The 7 comes from that the alignSize is 8 which is the number of bytes
305     //stored and sent together
306     return  ((num + 7) >> 3) << 3;
307   }
308 
309   /**
310    * Determines if we are running in a 32-bit JVM. Some unit tests need to
311    * know this too.
312    */
313   public static boolean is32BitJVM() {
314     return System.getProperty("sun.arch.data.model").equals("32");
315   }
316 
317 }
318