All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.commons.util.profiler.MemoryInspector Maven / Gradle / Ivy

/*
 * © Copyright IBM Corp. 2012-2013
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at:
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 * implied. See the License for the specific language governing 
 * permissions and limitations under the License.
 */

package com.ibm.commons.util.profiler;

import java.io.PrintStream;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.TextUtil;


/**
 * Estimate the size of an object in memory. This is just an estimation and is
 * used by profilers to give the developer a fair estimation of the memory.
 * 
 * To get a better estimation a use of the Java 1.5 Instrumentation API can be used as well.
 * 
 * @ibm-not-published 
 */
public class MemoryInspector {
    
    private static final String ENTRY_TAG = "Object"; // $NON-NLS-1$
    
    
    /** 
     * Callback class used by the Inspector when inspecting an hierarchy of objects.
     * @author priand
     */
    public interface Callback {
        public void begin();
        public void end(long size);
        public Object startObject(Stack params, Object parent, Field parentField, Object object);
        public void endObject(Stack params, Object object, long objectSize, long childrenSize);
    }
    
    /**
     * Simple Stack Class.
     */
    public interface Stack {
        public boolean isEmpty();
        public int size();
        public T pop();
        public void push(T o);
        public T get();
        public T get(int idx);
    }
    @SuppressWarnings("unchecked") // $NON-NLS-1$
    private static final class StackImpl implements Stack {
        private int count;
        private Object[] data;
        StackImpl() {
            data = new Object[128];
        }
        public boolean isEmpty() {
            return count==0;
        }
        public int size() {
            return count;
        }
        public T pop() {
            return (T)data[--count];
        }
        public void push(T o) {
            if(count==data.length) {
                Object[] nd = new Object[count+32];
                System.arraycopy(data, 0, nd, 0, count);
                data = nd;
            }
            data[count++] = o;
        }
        public T get() {
            return (T)data[count-1];
        }
        public T get(int idx) {
            return (T)data[count-idx-1];
        }
    }
    
    /**
     * Callback implementation that retains all the entries in memory.
     * @author priand
     */
    public static class CollectEntryCallBack implements Callback {
        public static class Entry {
            Entry       parent;
            Entry       next;
            Entry       firstChild;
            Field       parentField;
            Object      object;
            long        objectSize;
            long        childrenSize;
            public Entry(Entry parent, Field parentField, Object object) {
                this.parent = parent;
                this.parentField = parentField;
                this.object = object;
            }
            public Entry getParent() {
                return parent;
            }
            public boolean isRoot() {
            	return parent==null;
            }
            public Field getParentField() {
                return parentField;
            }
            public Object getObject() {
                return object;
            }
            public long getObjectSize() {
                return objectSize;
            }
            public long getChildrenSize() {
                return childrenSize;
            }
            public Entry getNext() {
                return next;
            }
            public Entry getFirstChild() {
                return firstChild;
            }
            void add(Entry child) {
                child.next = firstChild;
                firstChild = child;
            }
        }
        
        private Entry rootEntry;
        private Stack stack = new StackImpl();

        public CollectEntryCallBack() {
        }

        public Entry getRootEntry() {
            return rootEntry;
        }
        
        public Stack getEntryStack() {
            return stack;
        }

        public void begin() {
            this.rootEntry = createRootEntry();
            stack.push(rootEntry);
        }
        
        protected Entry createRootEntry() {
            return new Entry(null,null,""); // $NON-NLS-1$
        }
        
        public void end(long size) {
            this.rootEntry.childrenSize = size;
        }
        
        public Object startObject(Stack params, Object parent, Field parentField, Object object) {
            Entry e = createEntry(params,parent,parentField,object);
            if(isPersistent(params,parentField,object)) {
                stack.get().add(e);
                stack.push(e);
            }
            return e;
        }
        
        public void endObject(Stack params, Object object, long objectSize, long childrenSize) {
            Entry e = stack.get();
            if(e.object==object) {
                e.objectSize = objectSize;
                e.childrenSize = childrenSize;
                stack.pop();
            }
        }

        public Entry createEntry(Stack params, Object parent, Field parentField, Object object) {
            Entry e = new Entry((Entry)parent,parentField,object);
            return e;
        }
        
        public boolean isPersistent(Stack params, Field parentField, Object object) {
            return true;
        }
    }

    /**
     * Class that dumps a collection of entries.
     * 
     * @author priand
     */
    public static class CollectEntryDump {
        
        public enum Format {
            FORMAT_TEXT,
            FORMAT_XML
        }
        private CollectEntryCallBack callBack;
        private int initialLevel; 
        private Format format;
        
        public CollectEntryDump(CollectEntryCallBack callBack, Format format) {
            this.callBack = callBack;
            this.format = format;
        }
        
        public int getInitialLevel() {
            return initialLevel;
        }
        
        public void setInitialLevel(int initialLevel) {
            this.initialLevel = initialLevel;
        }
        
        public CollectEntryCallBack getCallBack() {
            return callBack;
        }
        
        public void dump(PrintStream ps) {
            dump(ps,callBack.getRootEntry(),initialLevel);
        }
        
        protected void dump(PrintStream ps, CollectEntryCallBack.Entry entry, int level) {
            boolean p = shouldDump(ps,entry,level);
            if(p) {
                printEntryStart(ps, entry, level);
                level++;
            }
            for( CollectEntryCallBack.Entry c=entry.getFirstChild(); c!=null; c=c.getNext()) {
                dump(ps,c,level);
            }
            if(p) {
                level--;
                printEntryEnd(ps, entry, level);
            }
        }
        protected boolean shouldDump(PrintStream ps, CollectEntryCallBack.Entry entry, int level) {
            return true;
        }
        protected void printEntryStart(PrintStream ps, CollectEntryCallBack.Entry entry, int level) {
            printIndent(ps, level);
            StringBuilder b = new StringBuilder();
            Object o = entry.getObject();
            if(format==Format.FORMAT_TEXT) {
                String fn = getFieldName(entry);
                if(fn!=null) {
                    b.append(fn);
                    b.append(':');
                }
                b.append(o.getClass().getSimpleName());
                if(o.getClass().isArray()) {
                    b.append('[');
                    b.append(Integer.toString(Array.getLength(o)));
                    b.append(']');
                }
                b.append(", Size="); // $NON-NLS-1$
                b.append(Long.toString(entry.getObjectSize()));
                b.append(", Total Size="); // $NON-NLS-1$
                b.append(Long.toString(entry.getObjectSize()+entry.getChildrenSize()));

                appendObjectString(b, o);
                
                ps.println(b.toString());
            } else if(format==Format.FORMAT_XML) {
                ps.print("<");
                ps.print(ENTRY_TAG);
                if(!entry.isRoot()) {
                	printXmlAttr(ps,"fieldName",getFieldName(entry)); // $NON-NLS-1$
                	String className = o.getClass().getSimpleName();
                	if(o.getClass().isArray()) {
                		className += '[' + Integer.toString(Array.getLength(o)) + ']';
                	}
                	printXmlAttr(ps,"class",className); // $NON-NLS-1$
                    printXmlAttr(ps,"size",Long.toString(entry.getObjectSize())); // $NON-NLS-1$
                }
                printXmlAttr(ps,"totalSize",Long.toString(entry.getObjectSize()+entry.getChildrenSize())); // $NON-NLS-1$
                
                appendObjectString(b, o);
                if(b.length()>0) {
                    printXmlAttr(ps,"value",b.toString()); // $NON-NLS-1$
                    b.setLength(0);
                }

                if(entry.getFirstChild()==null) {
                    ps.println("/>");
                } else {
                    ps.println(">");
                }
            }
        }
        protected void printXmlAttr(PrintStream ps, String attrName, String attrValue) {
            if(StringUtil.isNotEmpty(attrValue)) {
                ps.print(" ");
                ps.print(attrName);
                ps.print("='");
                ps.print(TextUtil.toXMLString(attrValue));
                ps.print("'");
            }
        }
        protected void printEntryEnd(PrintStream ps, CollectEntryCallBack.Entry entry, int level) {
            if(format==Format.FORMAT_XML) {
                if(entry.getFirstChild()!=null) {
                    printIndent(ps, level);
                    ps.print  ("");
                }
            }
        }
        protected String getFieldName(CollectEntryCallBack.Entry entry) {
            if(entry.getParentField()!=null) {
                return entry.getParentField().getName();
            }
            return null;
        }
        protected void appendObjectString(StringBuilder b, Object o) {
            if(o instanceof String) {
                String s = format(o.toString());
                if(b.length()>0) {
                    b.append(", ");
                }
                b.append(s);
                return;
            }
            if(o instanceof Number) {
                String s = o.toString();
                if(b.length()>0) {
                    b.append(", ");
                }
                b.append(s);
                b.append("");
                return;
            }
            if(o instanceof Map) {
                if(b.length()>0) {
                    b.append(", ");
                }
                b.append("count="); // $NON-NLS-1$
                b.append(((Map)o).size());
                return;
            }
            if(o instanceof Map.Entry) {
                if(b.length()>0) {
                    b.append(", ");
                }
                b.append("key="); // $NON-NLS-1$
                b.append(format(((Map.Entry)o).getKey().toString()));
                return;
            }
            if(o instanceof List) {
                if(b.length()>0) {
                    b.append(", ");
                }
                b.append("count="); // $NON-NLS-1$
                b.append(((List)o).size());
                return;
            }
            if(o instanceof Set) {
                if(b.length()>0) {
                    b.append(", ");
                }
                b.append("count="); // $NON-NLS-1$
                b.append(((Set)o).size());
                return;
            }
            if(o.getClass().isPrimitive() ){
                if(b.length()>0) {
                    b.append(", ");
                }
                b.append(o.toString());
                return;
            }
        }
        protected void printIndent(PrintStream ps, int level) {
            for(int i=0; i96) {
                s = s.substring(0,96) + "...";
            }
            return s;
        }
    }
    
    private Instrumentation instrumentation; 
    private Map visited = new IdentityHashMap();
    private IdentityHashMap, Field[]> fieldsCache = new IdentityHashMap, Field[]>(); 

    public MemoryInspector() {
        this(new IdentityHashMap(), new IdentityHashMap, Field[]>());
    }

    public MemoryInspector(Map visited, IdentityHashMap, Field[]> classCache) {
        this.instrumentation = JVMPIInterface.getInstrumentation();
        this.fieldsCache = classCache;
    }
    
    public Instrumentation getInstrumentation() {
        return instrumentation;
    }
    
    public Map getVisited() {
        return visited;
    }
    
    public long inspect(Object object, Callback cb) throws IllegalAccessException {
        StackImpl params = new StackImpl();
        long size = 0;
        cb.begin();
        params.push(object);
        try {
            size = inspect(params, null, null, object, cb);
            return size;
        } finally {
            cb.end(size);
            params.pop();
        }
    }

    protected long inspect(Stack params, Object parent, Field parentField, Object object, Callback cb) throws IllegalAccessException {
        // Look if the object should be skipped
        if(object == null) {
            return 0;
        }
        if(visited.containsKey(object)) {
            visited.put(object,Integer.valueOf(visited.get(object)+1));
            return 0;
        }
        
        // Case of an intern 
        if(isIntern(object)) {
            if(skipInterns(object)) {
                return 0;
            }
        }

        // Regular object
        long objectSize = 0;    // Size of the object itself
        long childrenSize = 0;  // Size of the contained children

        // Add it to the list of visited objects
        if(isStoreAsVisited(object)) {
            visited.put(object,Integer.valueOf(1));
        }

        if(instrumentation!=null) {
            // Store the object size coming from the Instrumentation interface
            objectSize = instrumentation.getObjectSize(object);
        }
        
        Object current = cb.startObject(params, parent, parentField, object);
        params.push(object);
        try {
            // We calculate the size of this object by browsing its fields
            Class clazz = object.getClass();
            if(clazz.isArray()) {
                // Java Array
                Class arrayClazz = clazz.getComponentType();
                if(!arrayClazz.isPrimitive()) {
                    int length = Array.getLength(object);
                    for(int i=0; i clazz) {
        Field[] f = fieldsCache.get(clazz);
        if(f!=null) {
            return f;
        }
        ArrayList ff = new ArrayList(); 
        Field[] fields = clazz.getDeclaredFields();
        for (int i=0; i params, Class fieldClass, Field field, Object obj, Object value) {
        return true;
    }  
}