001    /**
002     * Copyright (c) 2010, SIB. All rights reserved.
003     * 
004     * SIB (Swiss Institute of Bioinformatics) - http://www.isb-sib.ch Host -
005     * https://sourceforge.net/projects/javaprotlib/
006     * 
007     * Redistribution and use in source and binary forms, with or without
008     * modification, are permitted provided that the following conditions are met:
009     * Redistributions of source code must retain the above copyright notice, this
010     * list of conditions and the following disclaimer. Redistributions in binary
011     * form must reproduce the above copyright notice, this list of conditions and
012     * the following disclaimer in the documentation and/or other materials provided
013     * with the distribution. Neither the name of the SIB/GENEBIO nor the names of
014     * its contributors may be used to endorse or promote products derived from this
015     * software without specific prior written permission.
016     * 
017     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020     * ARE DISCLAIMED. IN NO EVENT SHALL SIB/GENEBIO BE LIABLE FOR ANY DIRECT,
021     * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
024     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027     */
028    package org.expasy.jpl.commons.base.mem;
029    
030    
031    import java.io.FileNotFoundException;
032    import java.io.FileOutputStream;
033    import java.io.PrintStream;
034    import java.lang.instrument.Instrumentation;
035    import java.lang.reflect.Array;
036    import java.lang.reflect.Field;
037    import java.lang.reflect.Modifier;
038    import java.util.IdentityHashMap;
039    import java.util.Map;
040    import java.util.Stack;
041    
042    
043    /**
044     * This agent trace sizeof of running objects.
045     * 
046     * Implemented and extended code from
047     * http://www.javaspecialists.eu/archive/Issue142.html
048     * 
049     * @author nikitin
050     * 
051     * @version 1.0.0
052     * 
053     */
054    public final class MemoryAgent {
055            
056            private static volatile Instrumentation instrumentation;
057            
058            private static PrintStream ps;
059            
060            /** Flyweight objects are immutable and in cache for reusability */
061            private static boolean isSharedFlyWeightProfiled = false;
062            private static boolean isTraceEnabled = false;
063            private static boolean isPrettyByte = false;
064            
065            /** Initializes agent */
066            public static void premain(final String agentArgs,
067                final Instrumentation instrumentation) {
068                    MemoryAgent.instrumentation = instrumentation;
069                    flushInStdout();
070            }
071            
072            /** flush output in file */
073            public static void flushInFile(String filename)
074                throws FileNotFoundException {
075                    ps = new PrintStream(new FileOutputStream(filename));
076            }
077            
078            /** flush output in standard output stream */
079            public static void flushInStdout() {
080                    ps = System.out;
081            }
082            
083            /** trace enabled/disabled */
084            public static void traceEnabled() {
085                    isTraceEnabled = true;
086            }
087            
088            public static void traceDisabled() {
089                    isTraceEnabled = false;
090            }
091            
092            public static void prettyByteEnabled() {
093                    isPrettyByte = true;
094            }
095            
096            public static void prettyByteDisabled() {
097                    isPrettyByte = false;
098            }
099            
100            private static void setCountSharedFlyWeight(final boolean bool) {
101                    isSharedFlyWeightProfiled = bool;
102            }
103            
104            public static void switchFlyweightProfilingOn() {
105                    setCountSharedFlyWeight(true);
106            }
107            
108            public static void switchFlyweightProfilingOff() {
109                    setCountSharedFlyWeight(false);
110            }
111            
112            /** Returns object size. */
113            public static long sizeOf(final Object obj) {
114                    if (instrumentation == null) {
115                            throw new IllegalStateException("MemoryAgent not initialised.");
116                    }
117                    
118                    if (!isSharedFlyWeightProfiled) {
119                            if (isSharedFlyweight(obj)) {
120                                    return 0;
121                            }
122                    }
123                    return instrumentation.getObjectSize(obj);
124            }
125            
126            /**
127             * Returns deep size of object, recursively iterating over its fields and
128             * superclasses.
129             */
130            public static long deepSizeOf(Object obj) {
131                    final Map<Object, Object> visited =
132                        new IdentityHashMap<Object, Object>();
133                    final Stack<Object> stack = new Stack<Object>();
134                    
135                    stack.push(obj);
136                    
137                    if (isTraceEnabled) {
138                            ps.println("push " + obj.getClass().getName());
139                    }
140                    long result = 0;
141                    do {
142                            obj = stack.pop();
143                            if (obj != null) {
144                                    // get the size of obj
145                                    final long size = internalSizeOf(obj, stack, visited);
146                                    
147                                    result += size;
148                                    
149                                    if (isTraceEnabled) {
150                                            ps.println("pop " + obj.getClass().getName() + ": " + obj);
151                                            ps.println("sizeof = " + size);
152                                            ps.println("sizeof cumul = " + result);
153                                    }
154                            } else {
155                                    if (isTraceEnabled) {
156                                            ps.println("[null object]");
157                                    }
158                            }
159                    } while (!stack.isEmpty());
160                    return result;
161            }
162            
163            public static void traceSizesOf(Object o) {
164                    long memShallow = MemoryAgent.sizeOf(o);
165                    long memDeep = MemoryAgent.deepSizeOf(o);
166                    
167                    String byteUnit = "B";
168                    
169                    String shalUnit = byteUnit;
170                    String deepUnit = byteUnit;
171                    
172                    if (isPrettyByte) {
173                            String kiloByteUnit = "KB";
174                            String megaByteUnit = "MB";
175                            
176                            if (memShallow / 1000 > 0) {
177                                    memShallow /= 1000;
178                                    shalUnit = kiloByteUnit;
179                                    if (memShallow / 1000 > 0) {
180                                            memShallow /= 1000;
181                                            shalUnit = megaByteUnit;
182                                    }
183                            }
184                            
185                            if (memDeep / 1000 > 0) {
186                                    memDeep /= 1000;
187                                    deepUnit = kiloByteUnit;
188                                    if (memDeep / 1000 > 0) {
189                                            memDeep /= 1000;
190                                            deepUnit = megaByteUnit;
191                                    }
192                            }
193                    }
194                    
195                    System.out.printf("%s, shallow=%d%s, deep=%d%s, flyweight=%s%n", o
196                        .getClass().getSimpleName(), memShallow, shalUnit, memDeep,
197                        deepUnit, (isSharedFlyWeightProfiled) ? "switch on" : "switch off");
198                    
199            }
200            
201            /**
202             * Returns true if this is a well-known shared flyweight. For example,
203             * interned Strings, Booleans and Number objects
204             */
205            @SuppressWarnings("unchecked")
206            public static boolean isSharedFlyweight(final Object obj) {
207                    // optimization - all of our flyweights are Comparable
208                    if (obj instanceof Comparable) {
209                            if (obj instanceof Enum) {
210                                    return true;
211                            } else if (obj instanceof String) {
212                                    return (obj == ((String) obj).intern());
213                            } else if (obj instanceof Boolean) {
214                                    return ((obj == Boolean.TRUE) || (obj == Boolean.FALSE));
215                            } else if (obj instanceof Integer) {
216                                    return (obj == Integer.valueOf((Integer) obj));
217                            } else if (obj instanceof Short) {
218                                    return (obj == Short.valueOf((Short) obj));
219                            } else if (obj instanceof Byte) {
220                                    return (obj == Byte.valueOf((Byte) obj));
221                            } else if (obj instanceof Long) {
222                                    return (obj == Long.valueOf((Long) obj));
223                            } else if (obj instanceof Character) {
224                                    return (obj == Character.valueOf((Character) obj));
225                            }
226                    }
227                    return false;
228            }
229            
230            private static boolean skipObject(final Object obj,
231                final Map<Object, Object> visited) {
232                    return (obj == null) || visited.containsKey(obj)
233                        || (!isSharedFlyWeightProfiled && isSharedFlyweight(obj));
234            }
235            
236            /**
237             * Visit all non static fields of {@code obj} (+ all super classes) to visit
238             * and push every objects in a stack to estimate interal size.
239             * 
240             * @param obj the object to estimate internal size.
241             * @param stack the stack of intern objects to estimate size later.
242             * @param visited a map of all visited objects.
243             * 
244             * @return the size of {@code obj}.
245             */
246            private static long internalSizeOf(final Object obj,
247                final Stack<Object> stack, final Map<Object, Object> visited) {
248                    StringBuffer sb = null;
249                    
250                    if (isTraceEnabled) {
251                            sb = new StringBuffer();
252                    }
253                    
254                    if (skipObject(obj, visited)) {
255                            
256                            if (isTraceEnabled) {
257                                    ps.println("skip " + obj + ", visited ? "
258                                        + visited.containsKey(obj) + ", is shared flyweight ? "
259                                        + isSharedFlyweight(obj));
260                            }
261                            return 0;
262                    }
263                    
264                    Class<?> clazz = obj.getClass();
265                    
266                    if (clazz.isArray()) {
267                            addArrayElementsToStack(clazz, obj, stack);
268                    } else {
269                            // add all non-primitive fields to the stack
270                            while (clazz != null) {
271                                    final Field[] fields = clazz.getDeclaredFields();
272                                    for (final Field field : fields) {
273                                            if (!Modifier.isStatic(field.getModifiers())
274                                                && !field.getType().isPrimitive()) {
275                                                    field.setAccessible(true);
276                                                    try {
277                                                            if (isTraceEnabled) {
278                                                                    sb.append("push field " + field.getName()
279                                                                        + ": " + field.getType().getSimpleName());
280                                                            }
281                                                            stack.add(field.get(obj));
282                                                    } catch (final IllegalAccessException ex) {
283                                                            throw new RuntimeException(ex);
284                                                    }
285                                            } else {
286                                                    if (isTraceEnabled) {
287                                                            sb.append("[skip static field " + field.getName()
288                                                                + ": " + field.getType().getSimpleName() + "]");
289                                                    }
290                                            }
291                                            if (isTraceEnabled) {
292                                                    sb.append("\n");
293                                            }
294                                    }
295                                    
296                                    clazz = clazz.getSuperclass();
297                                    
298                                    if (clazz != null) {
299                                            if (isTraceEnabled) {
300                                                    sb.append("[--> goto superclass "
301                                                        + clazz.getSimpleName() + "]");
302                                            }
303                                    }
304                            }
305                    }
306                    visited.put(obj, null);
307                    
308                    if (isTraceEnabled) {
309                            if (sb.length() > 0) {
310                                    ps.println(sb);
311                            }
312                    }
313                    return sizeOf(obj);
314            }
315            
316            private static void addArrayElementsToStack(final Class<?> clazz,
317                final Object obj, final Stack<Object> stack) {
318                    
319                    StringBuffer sb = null;
320                    
321                    if (isTraceEnabled) {
322                            sb = new StringBuffer();
323                            sb.append("[push all elements of array " + clazz.getName()
324                                + " (sz = " + Array.getLength(obj) + ")]\n");
325                    }
326                    
327                    if (!clazz.getComponentType().isPrimitive()) {
328                            final int length = Array.getLength(obj);
329                            for (int i = 0; i < length; i++) {
330                                    if (Array.get(obj, i) != null) {
331                                            if (isTraceEnabled) {
332                                                    sb.append("push element "
333                                                        + Array.get(obj, i).getClass().getSimpleName()
334                                                        + "\n");
335                                            }
336                                            stack.add(Array.get(obj, i));
337                                    } else {
338                                            if (isTraceEnabled) {
339                                                    sb.append("[skip null element]\n");
340                                            }
341                                    }
342                            }
343                    } else {
344                            if (isTraceEnabled) {
345                                    sb.append("[no elements to push for a primitive array]");
346                            }
347                    }
348                    
349                    if (isTraceEnabled) {
350                            ps.println(sb);
351                    }
352            }
353            
354    }