alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (ObjectSizeCalculator.java)

This example Java source code file (ObjectSizeCalculator.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

arrayelementsvisitor, assertionerror, class, classhistogramelement, classsizeinfo, field, identityhashmap, list, memorylayoutspecification, object, objectsizecalculator, override, reflection, string, unsupportedoperationexception, util

The ObjectSizeCalculator.java Java example source code

/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.ir.debug;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Contains utility methods for calculating the memory usage of objects. It
 * only works on the HotSpot JVM, and infers the actual memory layout (32 bit
 * vs. 64 bit word size, compressed object pointers vs. uncompressed) from
 * best available indicators. It can reliably detect a 32 bit vs. 64 bit JVM.
 * It can only make an educated guess at whether compressed OOPs are used,
 * though; specifically, it knows what the JVM's default choice of OOP
 * compression would be based on HotSpot version and maximum heap sizes, but if
 * the choice is explicitly overridden with the <tt>-XX:{+|-}UseCompressedOops command line
 * switch, it can not detect
 * this fact and will report incorrect sizes, as it will presume the default JVM
 * behavior.
 */

@SuppressWarnings("StaticNonFinalUsedInInitialization")
public class ObjectSizeCalculator {

    /**
     * Describes constant memory overheads for various constructs in a JVM implementation.
     */
    public interface MemoryLayoutSpecification {

        /**
         * Returns the fixed overhead of an array of any type or length in this JVM.
         *
         * @return the fixed overhead of an array.
         */
        int getArrayHeaderSize();

        /**
         * Returns the fixed overhead of for any {@link Object} subclass in this JVM.
         *
         * @return the fixed overhead of any object.
         */
        int getObjectHeaderSize();

        /**
         * Returns the quantum field size for a field owned by an object in this JVM.
         *
         * @return the quantum field size for an object.
         */
        int getObjectPadding();

        /**
         * Returns the fixed size of an object reference in this JVM.
         *
         * @return the size of all object references.
         */
        int getReferenceSize();

        /**
         * Returns the quantum field size for a field owned by one of an object's ancestor superclasses
         * in this JVM.
         *
         * @return the quantum field size for a superclass field.
         */
        int getSuperclassFieldPadding();
    }

    private static class CurrentLayout {
        private static final MemoryLayoutSpecification SPEC =
                getEffectiveMemoryLayoutSpecification();
    }

    /**
     * Given an object, returns the total allocated size, in bytes, of the object
     * and all other objects reachable from it.  Attempts to to detect the current JVM memory layout,
     * but may fail with {@link UnsupportedOperationException};
     *
     * @param obj the object; can be null. Passing in a {@link java.lang.Class} object doesn't do
     *          anything special, it measures the size of all objects
     *          reachable through it (which will include its class loader, and by
     *          extension, all other Class objects loaded by
     *          the same loader, and all the parent class loaders). It doesn't provide the
     *          size of the static fields in the JVM class that the Class object
     *          represents.
     * @return the total allocated size of the object and all other objects it
     *         retains.
     * @throws UnsupportedOperationException if the current vm memory layout cannot be detected.
     */
    public static long getObjectSize(final Object obj) throws UnsupportedOperationException {
        return obj == null ? 0 : new ObjectSizeCalculator(CurrentLayout.SPEC).calculateObjectSize(obj);
    }

    // Fixed object header size for arrays.
    private final int arrayHeaderSize;
    // Fixed object header size for non-array objects.
    private final int objectHeaderSize;
    // Padding for the object size - if the object size is not an exact multiple
    // of this, it is padded to the next multiple.
    private final int objectPadding;
    // Size of reference (pointer) fields.
    private final int referenceSize;
    // Padding for the fields of superclass before fields of subclasses are
    // added.
    private final int superclassFieldPadding;

    private final Map<Class classSizeInfos = new IdentityHashMap<>();


    private final Map<Object, Object> alreadyVisited = new IdentityHashMap<>();
    private final Map<Class histogram = new IdentityHashMap<>();

    private final Deque<Object> pending = new ArrayDeque<>(16 * 1024);
    private long size;

    /**
     * Creates an object size calculator that can calculate object sizes for a given
     * {@code memoryLayoutSpecification}.
     *
     * @param memoryLayoutSpecification a description of the JVM memory layout.
     */
    public ObjectSizeCalculator(final MemoryLayoutSpecification memoryLayoutSpecification) {
        memoryLayoutSpecification.getClass();
        arrayHeaderSize = memoryLayoutSpecification.getArrayHeaderSize();
        objectHeaderSize = memoryLayoutSpecification.getObjectHeaderSize();
        objectPadding = memoryLayoutSpecification.getObjectPadding();
        referenceSize = memoryLayoutSpecification.getReferenceSize();
        superclassFieldPadding = memoryLayoutSpecification.getSuperclassFieldPadding();
    }

    /**
     * Given an object, returns the total allocated size, in bytes, of the object
     * and all other objects reachable from it.
     *
     * @param obj the object; can be null. Passing in a {@link java.lang.Class} object doesn't do
     *          anything special, it measures the size of all objects
     *          reachable through it (which will include its class loader, and by
     *          extension, all other Class objects loaded by
     *          the same loader, and all the parent class loaders). It doesn't provide the
     *          size of the static fields in the JVM class that the Class object
     *          represents.
     * @return the total allocated size of the object and all other objects it
     *         retains.
     */
    public synchronized long calculateObjectSize(final Object obj) {
        // Breadth-first traversal instead of naive depth-first with recursive
        // implementation, so we don't blow the stack traversing long linked lists.
        histogram.clear();
        try {
            for (Object o = obj;;) {
                visit(o);
                if (pending.isEmpty()) {
                    return size;
                }
                o = pending.removeFirst();
            }
        } finally {
            alreadyVisited.clear();
            pending.clear();
            size = 0;
        }
    }

    /**
     * Get the class histograpm
     * @return class histogram element list
     */
    public List<ClassHistogramElement> getClassHistogram() {
        return new ArrayList<>(histogram.values());
    }

    private ClassSizeInfo getClassSizeInfo(final Class<?> clazz) {
        ClassSizeInfo csi = classSizeInfos.get(clazz);
        if(csi == null) {
            csi = new ClassSizeInfo(clazz);
            classSizeInfos.put(clazz, csi);
        }
        return csi;
    }

    private void visit(final Object obj) {
        if (alreadyVisited.containsKey(obj)) {
            return;
        }
        final Class<?> clazz = obj.getClass();
        if (clazz == ArrayElementsVisitor.class) {
            ((ArrayElementsVisitor) obj).visit(this);
        } else {
            alreadyVisited.put(obj, obj);
            if (clazz.isArray()) {
                visitArray(obj);
            } else {
                getClassSizeInfo(clazz).visit(obj, this);
            }
        }
    }

    private void visitArray(final Object array) {
        final Class<?> arrayClass = array.getClass();
        final Class<?> componentType = arrayClass.getComponentType();
        final int length = Array.getLength(array);
        if (componentType.isPrimitive()) {
            increaseByArraySize(arrayClass, length, getPrimitiveFieldSize(componentType));
        } else {
            increaseByArraySize(arrayClass, length, referenceSize);
            // If we didn't use an ArrayElementsVisitor, we would be enqueueing every
            // element of the array here instead. For large arrays, it would
            // tremendously enlarge the queue. In essence, we're compressing it into
            // a small command object instead. This is different than immediately
            // visiting the elements, as their visiting is scheduled for the end of
            // the current queue.
            switch (length) {
            case 0: {
                break;
            }
            case 1: {
                enqueue(Array.get(array, 0));
                break;
            }
            default: {
                enqueue(new ArrayElementsVisitor((Object[]) array));
            }
            }
        }
    }

    private void increaseByArraySize(final Class<?> clazz, final int length, final long elementSize) {
        increaseSize(clazz, roundTo(arrayHeaderSize + length * elementSize, objectPadding));
    }

    private static class ArrayElementsVisitor {
        private final Object[] array;

        ArrayElementsVisitor(final Object[] array) {
            this.array = array;
        }

        public void visit(final ObjectSizeCalculator calc) {
            for (final Object elem : array) {
                if (elem != null) {
                    calc.visit(elem);
                }
            }
        }
    }

    void enqueue(final Object obj) {
        if (obj != null) {
            pending.addLast(obj);
        }
    }

    void increaseSize(final Class<?> clazz, final long objectSize) {
        ClassHistogramElement he = histogram.get(clazz);
        if(he == null) {
            he = new ClassHistogramElement(clazz);
            histogram.put(clazz, he);
        }
        he.addInstance(objectSize);
        size += objectSize;
    }

    static long roundTo(final long x, final int multiple) {
        return ((x + multiple - 1) / multiple) * multiple;
    }

    private class ClassSizeInfo {
        // Padded fields + header size
        private final long objectSize;
        // Only the fields size - used to calculate the subclasses' memory
        // footprint.
        private final long fieldsSize;
        private final Field[] referenceFields;

        public ClassSizeInfo(final Class<?> clazz) {
            long newFieldsSize = 0;
            final List<Field> newReferenceFields = new LinkedList<>();
            for (Field f : clazz.getDeclaredFields()) {
                if (Modifier.isStatic(f.getModifiers())) {
                    continue;
                }
                final Class<?> type = f.getType();
                if (type.isPrimitive()) {
                    newFieldsSize += getPrimitiveFieldSize(type);
                } else {
                    f.setAccessible(true);
                    newReferenceFields.add(f);
                    newFieldsSize += referenceSize;
                }
            }
            final Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                final ClassSizeInfo superClassInfo = getClassSizeInfo(superClass);
                newFieldsSize += roundTo(superClassInfo.fieldsSize, superclassFieldPadding);
                newReferenceFields.addAll(Arrays.asList(superClassInfo.referenceFields));
            }
            this.fieldsSize = newFieldsSize;
            this.objectSize = roundTo(objectHeaderSize + newFieldsSize, objectPadding);
            this.referenceFields = newReferenceFields.toArray(
                    new Field[newReferenceFields.size()]);
        }

        void visit(final Object obj, final ObjectSizeCalculator calc) {
            calc.increaseSize(obj.getClass(), objectSize);
            enqueueReferencedObjects(obj, calc);
        }

        public void enqueueReferencedObjects(final Object obj, final ObjectSizeCalculator calc) {
            for (Field f : referenceFields) {
                try {
                    calc.enqueue(f.get(obj));
                } catch (IllegalAccessException e) {
                    final AssertionError ae = new AssertionError(
                            "Unexpected denial of access to " + f);
                    ae.initCause(e);
                    throw ae;
                }
            }
        }
    }

    private static long getPrimitiveFieldSize(final Class<?> type) {
        if (type == boolean.class || type == byte.class) {
            return 1;
        }
        if (type == char.class || type == short.class) {
            return 2;
        }
        if (type == int.class || type == float.class) {
            return 4;
        }
        if (type == long.class || type == double.class) {
            return 8;
        }
        throw new AssertionError("Encountered unexpected primitive type " +
                type.getName());
    }

    // ALERT: java.lang.management is not available in compact 1.  We need
    // to use reflection to soft link test memory statistics.

    static Class<?>  managementFactory    = null;
    static Class<?>  memoryPoolMXBean     = null;
    static Class<?>  memoryUsage          = null;
    static Method    getMemoryPoolMXBeans = null;
    static Method    getUsage             = null;
    static Method    getMax               = null;
    static {
        try {
            managementFactory    = Class.forName("java.lang.management.ManagementFactory");
            memoryPoolMXBean     = Class.forName("java.lang.management.MemoryPoolMXBean");
            memoryUsage          = Class.forName("java.lang.management.MemoryUsage");

            getMemoryPoolMXBeans = managementFactory.getMethod("getMemoryPoolMXBeans");
            getUsage             = memoryPoolMXBean.getMethod("getUsage");
            getMax               = memoryUsage.getMethod("getMax");
        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) {
            // Pass thru, asserts when attempting to use.
        }
    }

    /**
     * Return the current memory usage
     * @return current memory usage derived from system configuration
     */
    public static MemoryLayoutSpecification getEffectiveMemoryLayoutSpecification() {
        final String vmName = System.getProperty("java.vm.name");
        if (vmName == null || !vmName.startsWith("Java HotSpot(TM) ")) {
            throw new UnsupportedOperationException(
                    "ObjectSizeCalculator only supported on HotSpot VM");
        }

        final String dataModel = System.getProperty("sun.arch.data.model");
        if ("32".equals(dataModel)) {
            // Running with 32-bit data model
            return new MemoryLayoutSpecification() {
                @Override public int getArrayHeaderSize() {
                    return 12;
                }
                @Override public int getObjectHeaderSize() {
                    return 8;
                }
                @Override public int getObjectPadding() {
                    return 8;
                }
                @Override public int getReferenceSize() {
                    return 4;
                }
                @Override public int getSuperclassFieldPadding() {
                    return 4;
                }
            };
        } else if (!"64".equals(dataModel)) {
            throw new UnsupportedOperationException("Unrecognized value '" +
                    dataModel + "' of sun.arch.data.model system property");
        }

        final String strVmVersion = System.getProperty("java.vm.version");
        final int vmVersion = Integer.parseInt(strVmVersion.substring(0,
                strVmVersion.indexOf('.')));
        if (vmVersion >= 17) {
            long maxMemory = 0;

            /*
               See ALERT above.  The reflection code below duplicates the following
               sequence, and avoids hard coding of java.lang.management.

               for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) {
                   maxMemory += mp.getUsage().getMax();
               }
            */

            if (getMemoryPoolMXBeans == null) {
                throw new AssertionError("java.lang.management not available in compact 1");
            }

            try {
                final List<?> memoryPoolMXBeans = (List)getMemoryPoolMXBeans.invoke(managementFactory);
                for (final Object mp : memoryPoolMXBeans) {
                    final Object usage = getUsage.invoke(mp);
                    final Object max = getMax.invoke(usage);
                    maxMemory += ((Long)max).longValue();
                }
            } catch (IllegalAccessException |
                     IllegalArgumentException |
                     InvocationTargetException ex) {
                throw new AssertionError("java.lang.management not available in compact 1");
            }

            if (maxMemory < 30L * 1024 * 1024 * 1024) {
                // HotSpot 17.0 and above use compressed OOPs below 30GB of RAM total
                // for all memory pools (yes, including code cache).
                return new MemoryLayoutSpecification() {
                    @Override public int getArrayHeaderSize() {
                        return 16;
                    }
                    @Override public int getObjectHeaderSize() {
                        return 12;
                    }
                    @Override public int getObjectPadding() {
                        return 8;
                    }
                    @Override public int getReferenceSize() {
                        return 4;
                    }
                    @Override public int getSuperclassFieldPadding() {
                        return 4;
                    }
                };
            }
        }

        // In other cases, it's a 64-bit uncompressed OOPs object model
        return new MemoryLayoutSpecification() {
            @Override public int getArrayHeaderSize() {
                return 24;
            }
            @Override public int getObjectHeaderSize() {
                return 16;
            }
            @Override public int getObjectPadding() {
                return 8;
            }
            @Override public int getReferenceSize() {
                return 8;
            }
            @Override public int getSuperclassFieldPadding() {
                return 8;
            }
        };
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java ObjectSizeCalculator.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.