001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.util;
011:
012: // necessary for SizeOf
013: import java.lang.reflect.Array;
014: import java.lang.reflect.Field;
015: import java.lang.reflect.Modifier;
016: import java.util.*;
017:
018: import org.mmbase.util.logging.Logger;
019: import org.mmbase.util.logging.Logging;
020:
021: /**
022: *<p>
023: * Implementation of 'sizeof'. It is very hard to get reasonable estimations of how much memory your
024: * structures take, this class it trying it any way. Often it's quite a heavy operation, so don't
025: * use it too much.
026: *</p>
027: * <p>
028: * A count of the byte size of an object is done recursively and for every count a SizeOf instance
029: * must be instantiated. The static {@link #getByteSize(Object)} does that for you.
030: *</p>
031: * <p>
032: * The core of the SizeOf object is then {@link #sizeof(Object)} plus a (private) 'countedObjects'
033: * collection. The sizeof method returns the size the given Object would increase the size of this
034: * countedObjects administration. This means that it returns 0 if the Object was already measured by the SizeOf
035: * instance.
036: * </p>
037: * <p>
038: * This means that it can be tricky to interpret the results of sizeof. The basic rule is that you
039: * should take the size of a lot of similar objects with the same SizeOf instance, and take the
040: * average.
041: * </p>
042: * <p>
043: * A good example is {@link org.mmbase.module.core.MMObjectNode}. The first one counted will also
044: * count a {@link org.mmbase.module.core.MMObjectBuilder} object - because that is linked to it, so
045: * its memory is also taken (indirectly) - and will therefor give an unexpectedly large value like 20
046: * kb or so. The second MMObjectNode, of the same type, that you'd count would however give a much
047: * better estimation of the memory used by one Node in MMBase. The MMObjectBuilder is not counted
048: * any more in this second Object, because it was already counted because of the first one.
049: * </p>
050: * <p>
051: * For every individual entity there are several strategies for guessing the size.
052: * <ul>
053: * <li>For atomic types (boolean, byte, char, short, int, long, float, double) a constant value is
054: * returned</li>
055: * <li>If the entity implements {@link SizeMeasurable} it uses {@link SizeMeasurable#getByteSize(SizeOf)}</li>
056: * <li>For a limited number of known classes, a reasonable guess is done. E.g. if it is a {@link
057: * java.util.Collection} it will simply sum the results of sizeof of its elements, and for a String
058: * it will return getBytes().length().</li>
059: * <li>If all that fails, reflection will be used to sum the results of sizeof of all readable
060: * members.</li>
061: * </ul>
062: * </p>
063: * <p>
064: * Don't forget to dereference or clear the SizeOf after use, otherwise it itself is a memory leak.
065: *</p>
066: *
067: * @author Michiel Meeuwissen
068: * @since MMBase-1.6
069: * @version $Id: SizeOf.java,v 1.20 2007/02/25 17:56:58 nklasens Exp $
070: * @todo We need to know how well this actually works...
071: */
072: public class SizeOf {
073: private static final Logger log = Logging
074: .getLoggerInstance(SizeOf.class);
075:
076: public static final int SZ_REF = 4;
077:
078: private static int size_prim(Class<?> t) {
079: if (t == Boolean.TYPE)
080: return 1;
081: else if (t == Byte.TYPE)
082: return 1;
083: else if (t == Character.TYPE)
084: return 2;
085: else if (t == Short.TYPE)
086: return 2;
087: else if (t == Integer.TYPE)
088: return 4;
089: else if (t == Long.TYPE)
090: return 8;
091: else if (t == Float.TYPE)
092: return 4;
093: else if (t == Double.TYPE)
094: return 8;
095: else if (t == Void.TYPE)
096: return 0;
097: else
098: return SZ_REF;
099: }
100:
101: public static int sizeof(boolean b) {
102: return 1;
103: }
104:
105: public static int sizeof(byte b) {
106: return 1;
107: }
108:
109: public static int sizeof(char c) {
110: return 2;
111: }
112:
113: public static int sizeof(short s) {
114: return 2;
115: }
116:
117: public static int sizeof(int i) {
118: return 4;
119: }
120:
121: public static int sizeof(long l) {
122: return 8;
123: }
124:
125: public static int sizeof(float f) {
126: return 4;
127: }
128:
129: public static int sizeof(double d) {
130: return 8;
131: }
132:
133: // To avoid infinite loops (cyclic references):
134: private Set<Object> countedObjects = new HashSet<Object>();
135:
136: public static int getByteSize(Object obj) {
137: return new SizeOf().sizeof(obj);
138: }
139:
140: /**
141: * Makes this SizeOf object ready for reuse.
142: * @since MMBase-1.8
143: */
144: public void clear() {
145: countedObjects.clear();
146: }
147:
148: /**
149: * @return The size in bytes obj structure will take, or <code>0</code> if the object was
150: * already counted by this SizeOf object.
151: */
152: public int sizeof(Object obj) {
153: if (obj == null) {
154: return 0;
155: }
156:
157: if (!countedObjects.add(obj)) {
158: return 0;
159: }
160:
161: Class<?> c = obj.getClass();
162:
163: if (c.isArray()) {
164: log.debug("an array");
165: return size_arr(obj, c);
166: } else {
167: if (log.isDebugEnabled()) {
168: log.debug("an object " + obj + " " + c);
169: }
170: try {
171: if (SizeMeasurable.class.isAssignableFrom(c))
172: return sizeof((SizeMeasurable) obj);
173: if (javax.servlet.http.HttpSession.class
174: .isAssignableFrom(c))
175: return sizeof((javax.servlet.http.HttpSession) obj);
176: if (org.w3c.dom.Node.class.isAssignableFrom(c))
177: return sizeof((org.w3c.dom.Node) obj);
178: if (Map.class.isAssignableFrom(c))
179: return sizeof((Map<?, ?>) obj);
180: if (Collection.class.isAssignableFrom(c))
181: return sizeof((Collection<?>) obj);
182: if (String.class.isAssignableFrom(c))
183: return sizeof((String) obj);
184: // it's odd that all these seem to cost 16 bytes each, but this is a result of simply trying it on Sun JVM 1.5
185: // See also http://www.javaworld.com/javaworld/javatips/jw-javatip130.html
186: if (Integer.class.isAssignableFrom(c))
187: return 16;
188: if (Long.class.isAssignableFrom(c))
189: return 16;
190: if (Float.class.isAssignableFrom(c))
191: return 16;
192: if (Double.class.isAssignableFrom(c))
193: return 16;
194: // more insteresting stuff can be added here.
195: } catch (Throwable e) {
196: log.warn("Error during determination of size of " + obj
197: + " :" + e.getMessage(), e);
198: }
199: return size_inst(obj, c);
200: }
201: }
202:
203: private int sizeof(Map<?, ?> m) {
204: int len = size_inst(m, m.getClass()) + m.size() * 30; // estimated overhead per entry. Is about correct for Hashtable and HashMap
205: Iterator<? extends Map.Entry<?, ?>> i = m.entrySet().iterator();
206: while (i.hasNext()) {
207: Map.Entry<?, ?> entry = i.next();
208: len += sizeof(entry.getKey());
209: len += sizeof(entry.getValue());
210: }
211: return len;
212: }
213:
214: private int sizeof(Collection<?> m) {
215: log.debug("sizeof List");
216: int len = size_inst(m, m.getClass());
217: Iterator<?> i = m.iterator();
218: while (i.hasNext()) {
219: len += sizeof(i.next());
220: }
221: return len;
222: }
223:
224: private int sizeof(javax.servlet.http.HttpSession session) {
225: log.debug("sizeof HttpSession");
226: int len = size_inst(session, session.getClass());
227: Enumeration<String> e = session.getAttributeNames();
228: while (e.hasMoreElements()) {
229: String attribute = e.nextElement();
230: len += sizeof(attribute);
231: len += sizeof(session.getAttribute(attribute));
232: }
233: return len;
234: }
235:
236: private int sizeof(org.w3c.dom.Node node) {
237: log.debug("sizeof Node");
238: // a little hackish...
239: return sizeof(org.mmbase.util.xml.XMLWriter.write(node, false));
240: }
241:
242: private int sizeof(String m) {
243: // just a guess, but on a Sun 1.5 JVM this is pretty good.
244: return 44 + m.length() * 2; // probably 11 (!) references + array of 16 bits unicode?
245: }
246:
247: private int sizeof(SizeMeasurable m) {
248: if (log.isDebugEnabled()) {
249: log.debug("sizeof SizeMeasureable " + m);
250: }
251: return m.getByteSize(this );
252: }
253:
254: private int size_inst(Object obj, Class<?> c) {
255: Field flds[] = c.getDeclaredFields();
256: int sz = 8; // wild guess for the size of an Object. (reference + hashcode?
257:
258: for (Field f : flds) {
259: if (!c.isInterface()
260: && (f.getModifiers() & Modifier.STATIC) != 0) {
261: continue;
262: }
263: sz += size_prim(f.getType());
264: //f.setAccessible(true);
265: if (f.isAccessible()) {
266: try {
267: sz += sizeof(f.get(obj)); // recursion
268: } catch (java.lang.IllegalAccessException e) {
269: // well...
270: log.trace(e);
271: }
272: }
273: }
274:
275: if (c.getSuperclass() != null) {
276: sz += size_inst(obj, c.getSuperclass()) - 8; // 8: already guessed
277: }
278:
279: Class<?> cv[] = c.getInterfaces();
280: for (Class<?> element : cv) {
281: sz += size_inst(obj, element) - 8; // 8: already guessed
282: }
283:
284: return sz;
285: }
286:
287: private int size_arr(Object obj, Class<?> c) {
288: Class<?> ct = c.getComponentType();
289: int len = Array.getLength(obj);
290:
291: if (ct.isPrimitive()) {
292: return len * size_prim(ct);
293: } else {
294: int sz = 0;
295: for (int i = 0; i < len; i++) {
296: Object obj2 = Array.get(obj, i);
297: sz += sizeof(obj2);
298: }
299: return sz;
300: }
301: }
302:
303: public static void main(String argv[]) throws InterruptedException {
304: final Runtime rt = Runtime.getRuntime();
305: final int SIZE = Math.round((float) Math.pow(10, 4)); // I hate java
306:
307: //final Object[] list = new Object[SIZE];
308: List<Integer> list = new ArrayList<Integer>();
309: //ArrayList list = new ArrayList();
310: //Map map = new HashMap();
311:
312: rt.runFinalization();
313: rt.gc();
314: Thread.sleep(1000);
315: long usedBefore = rt.totalMemory() - rt.freeMemory();
316: // create one million objects
317: for (int i = SIZE; i < 2 * SIZE; i++) {
318: //list[i - 1000000] = "a" + i + "b" + i;
319: //list.add("a" + i + "b" + i); // of 16 byte
320: //list.add(new String( new byte[] {})); // of 0 byte
321: //list.add(new String( new byte[] {}).intern());
322: //map.put("a" + i , "b" + i); // of 16 byte
323: list.add(i);
324: //list[i - SIZE] = new Double(i);
325: }
326: rt.runFinalization();
327: rt.gc();
328: Thread.sleep(1000);
329: long usedAfter = rt.totalMemory() - rt.freeMemory();
330: System.out.println("" + SIZE + " of Integer costs "
331: + (usedAfter - usedBefore) + " bytes");
332: System.out.println("Sizeof reports: "
333: + SizeOf.getByteSize(list) + " bytes");
334: //System.out.println("" + list);
335:
336: }
337: }
|