1 package org.apache.lucene.util;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import java.lang.reflect.*;
21 import java.text.DecimalFormat;
25 * Estimates the size of a given Object using a given MemoryModel for primitive
30 * Internally uses a Map to temporally hold a reference to every
33 * If checkInterned, all Strings checked will be interned, but those
34 * that were not already interned will be released for GC when the
35 * estimate is complete.
39 public final class RamUsageEstimator {
41 public final static int NUM_BYTES_SHORT = 2;
42 public final static int NUM_BYTES_INT = 4;
43 public final static int NUM_BYTES_LONG = 8;
44 public final static int NUM_BYTES_FLOAT = 4;
45 public final static int NUM_BYTES_DOUBLE = 8;
46 public final static int NUM_BYTES_CHAR = 2;
47 public final static int NUM_BYTES_OBJECT_HEADER = 8;
48 public final static int NUM_BYTES_OBJECT_REF = Constants.JRE_IS_64BIT ? 8 : 4;
49 public final static int NUM_BYTES_ARRAY_HEADER = NUM_BYTES_OBJECT_HEADER + NUM_BYTES_INT + NUM_BYTES_OBJECT_REF;
51 private MemoryModel memoryModel;
53 private final Map<Object,Object> seen;
56 private int arraySize;
57 private int classSize;
59 private boolean checkInterned;
62 * Constructs this object with an AverageGuessMemoryModel and
63 * checkInterned = true.
65 public RamUsageEstimator() {
66 this(new AverageGuessMemoryModel());
70 * @param checkInterned check if Strings are interned and don't add to size
71 * if they are. Defaults to true but if you know the objects you are checking
72 * won't likely contain many interned Strings, it will be faster to turn off
75 public RamUsageEstimator(boolean checkInterned) {
76 this(new AverageGuessMemoryModel(), checkInterned);
80 * @param memoryModel MemoryModel to use for primitive object sizes.
82 public RamUsageEstimator(MemoryModel memoryModel) {
83 this(memoryModel, true);
87 * @param memoryModel MemoryModel to use for primitive object sizes.
88 * @param checkInterned check if Strings are interned and don't add to size
89 * if they are. Defaults to true but if you know the objects you are checking
90 * won't likely contain many interned Strings, it will be faster to turn off
93 public RamUsageEstimator(MemoryModel memoryModel, boolean checkInterned) {
94 this.memoryModel = memoryModel;
95 this.checkInterned = checkInterned;
96 // Use Map rather than Set so that we can use an IdentityHashMap - not
97 // seeing an IdentityHashSet
98 seen = new IdentityHashMap<Object,Object>(64);
99 this.refSize = memoryModel.getReferenceSize();
100 this.arraySize = memoryModel.getArraySize();
101 this.classSize = memoryModel.getClassSize();
104 public long estimateRamUsage(Object obj) {
105 long size = size(obj);
110 private long size(Object obj) {
114 // interned not part of this object
115 if (checkInterned && obj instanceof String
116 && obj == ((String) obj).intern()) { // interned string will be eligible
118 // estimateRamUsage(Object) return
122 // skip if we have seen before
123 if (seen.containsKey(obj)) {
130 Class<?> clazz = obj.getClass();
131 if (clazz.isArray()) {
132 return sizeOfArray(obj);
137 // walk type hierarchy
138 while (clazz != null) {
139 Field[] fields = clazz.getDeclaredFields();
140 for (int i = 0; i < fields.length; i++) {
141 if (Modifier.isStatic(fields[i].getModifiers())) {
145 if (fields[i].getType().isPrimitive()) {
146 size += memoryModel.getPrimitiveSize(fields[i].getType());
149 fields[i].setAccessible(true);
151 Object value = fields[i].get(obj);
155 } catch (IllegalAccessException ex) {
161 clazz = clazz.getSuperclass();
167 private long sizeOfArray(Object obj) {
168 int len = Array.getLength(obj);
172 long size = arraySize;
173 Class<?> arrayElementClazz = obj.getClass().getComponentType();
174 if (arrayElementClazz.isPrimitive()) {
175 size += len * memoryModel.getPrimitiveSize(arrayElementClazz);
177 for (int i = 0; i < len; i++) {
178 size += refSize + size(Array.get(obj, i));
185 private static final long ONE_KB = 1024;
186 private static final long ONE_MB = ONE_KB * ONE_KB;
187 private static final long ONE_GB = ONE_KB * ONE_MB;
190 * Return good default units based on byte size.
192 public static String humanReadableUnits(long bytes, DecimalFormat df) {
193 String newSizeAndUnits;
195 if (bytes / ONE_GB > 0) {
196 newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_GB))
198 } else if (bytes / ONE_MB > 0) {
199 newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_MB))
201 } else if (bytes / ONE_KB > 0) {
202 newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_KB))
205 newSizeAndUnits = String.valueOf(bytes) + " bytes";
208 return newSizeAndUnits;