Java获取对象大小

来源

关于模拟jdk.nashorn.internal.ir.debug.ObjectSizeCalculator.getObjectSize(Object o)获取java对象大小
使用估计内存计算的方式对比ObjectSizeCalculator.getObjectSize()方法大小
参考:https://www.cnblogs.com/shown/p/6211179.html
不想搞agent就想直接获取;然而有的jdk或高版本的jdk已经没有ObjectSizeCalculator.getObjectSize()这个方法了,记录一下这种内存的方式:

结论

import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator;

import java.util.HashMap;
import java.util.Map;

public class ObjectSizeCalculatorCom {

    public static void main(String[] args) throws Exception {
        // 创建两个对象验证
        String myObject = "Hello, world!";
        Map<String, String> map = new HashMap<String, String>();
        map.put("key1","value1");
        map.put("key2","value2");
        map.put("key3","value3");

        // 使用ObjectSizeCalculator计算对象大小
        long size1 = ObjectSizeCalculator.getObjectSize(map);
        System.out.println("Size with ObjectSizeCalculator: " + size1);

        // 内存方式对比结果
        System.out.println("Size with memory: " + calcSize(map));
    }
    /**
     * 计算缓存大小.
     * @param object
     * @return
     */
    private static long calcSize(Object object) {
        ObjectMemoryCalculationInfo objectInfo;
        try {
            objectInfo = new ObjectMemoryCalculator().calculateMemory(object);
        } catch (IllegalAccessException e) {
            return 0;
        }
        return objectInfo.calculateDeepSize();
    }
}

输出结果

//String
Size with ObjectSizeCalculator: 72
Size with memory: 72
//map
Size with ObjectSizeCalculator: 536
Size with memory: 544

参考代码

import java.util.*;

/**
 * 对象内存计算信息类,用于计算对象及其内存布局的信息。
 *
 * @author HH287
 * @version 1.0.0 2024/4/8 17:25
 * @since JDK 1.8.0
 */
public class ObjectMemoryCalculationInfo {

    public final int arrayElementSize; // 数组元素大小
    public final int arraySize; // 数组大小
    public final List<ObjectMemoryCalculationInfo> childObjects; // 子对象列表
    public final int objectOffset; // 对象偏移量
    public final int objectLength; // 对象长度
    public final int arrayBaseAddress; // 数组基地址
    long totalSize = 0L; // 总大小

    /**
     * 构造函数,初始化对象内存计算信息。
     *
     * @param name 对象名称
     * @param type 对象类型
     * @param contents 对象内容
     * @param objectOffset 对象偏移量
     * @param objectLength 对象长度
     * @param arraySize 数组大小
     * @param arrayBaseAddress 数组基地址
     * @param arrayElementSize 数组元素大小
     */
    public ObjectMemoryCalculationInfo(String name, String type, String contents, int objectOffset, int objectLength, int arraySize, int arrayBaseAddress, int arrayElementSize) {
        this.objectOffset = objectOffset;
        this.objectLength = objectLength;
        this.arraySize = arraySize;
        this.arrayBaseAddress = arrayBaseAddress;
        this.arrayElementSize = arrayElementSize;
        this.childObjects = new ArrayList<>(1);
    }

    /**
     * 添加子对象。
     *
     * @param childObject 子对象的内存计算信息
     */
    public void addChild(ObjectMemoryCalculationInfo childObject) {
        if (childObject != null) {
            this.childObjects.add(childObject);
        }
    }

    /**
     * 计算对象及其子对象的深度大小。
     *
     * @return 对象的深度大小
     */
    public long calculateDeepSize() {
        return this.addPaddingSize((long)this.arraySize + this.calculateUnderlyingSize(this.arraySize != 0));
    }

    /**
     * 计算对象及其子对象的底层大小。
     *
     * @param isArray 是否为数组
     * @return 底层大小
     */
    private long calculateUnderlyingSize(boolean isArray) {
        // 遍历子对象,累加它们的大小
        ObjectMemoryCalculationInfo child;
        for (Iterator<ObjectMemoryCalculationInfo> iterator = this.childObjects.iterator(); iterator.hasNext(); this.totalSize += (long)child.arraySize + child.calculateUnderlyingSize(child.arraySize != 0)) {
            child = iterator.next();
        }

        // 如果不是数组且子对象不为空,则对最后一个子对象的末尾添加对齐填充
        if (!isArray && !this.childObjects.isEmpty()) {
            int lastChildEnd = ((ObjectMemoryCalculationInfo)this.childObjects.get(this.childObjects.size() - 1)).objectOffset + ((ObjectMemoryCalculationInfo)this.childObjects.get(this.childObjects.size() - 1)).objectLength;
            this.totalSize += this.addPaddingSize((long)lastChildEnd);
        }

        return this.totalSize;
    }

    /**
     * 根据偏移量对子对象进行排序。
     */
    public void sortChildrenByOffset() {
        this.childObjects.sort(new OffsetComparator());
    }

    /**
     * 生成对象及其子对象的字符串表示。
     *
     * @return 对象的字符串表示
     */
    public String toString() {
        StringBuilder sb = new StringBuilder();
        this.buildToStringHelper(sb, 0);
        return sb.toString();
    }

    /**
     * 辅助方法,生成对象及其子对象的字符串表示。
     *
     * @param sb 字符串构建器
     * @param depth 递归深度
     */
    private void buildToStringHelper(StringBuilder sb, int depth) {
        this.indent(sb, depth).append(", objectOffset=").append(this.objectOffset).append(", objectLength=").append(this.objectLength);
        if (this.arraySize > 0) {
            sb.append(", arrayBaseAddress=").append(this.arrayBaseAddress);
            sb.append(", arrayElementSize=").append(this.arrayElementSize);
            sb.append(", arraySize=").append(this.arraySize);
        }

        // 遍历并递归生成子对象的字符串表示
        Iterator<ObjectMemoryCalculationInfo> childIterator = this.childObjects.iterator();

        while (childIterator.hasNext()) {
            ObjectMemoryCalculationInfo child = childIterator.next();
            sb.append('\n');
            child.buildToStringHelper(sb, depth + 1);
        }

    }

    /**
     * 在字符串构建器中添加缩进。
     *
     * @param sb 字符串构建器
     * @param depth 递归深度
     * @return 缩进后的字符串构建器
     */
    private StringBuilder indent(StringBuilder sb, int depth) {
        for (int i = 0; i < depth; ++i) {
            sb.append("\t");
        }

        return sb;
    }

    /**
     * 计算并添加对齐填充大小。
     *
     * @param size 原始大小
     * @return 添加对齐填充后的大小
     */
    private long addPaddingSize(long size) {
        return size % 8L != 0L ? (size / 8L + 1L) * 8L : size;
    }

    /**
     * 内部类,用于偏移量的比较,实现Comparator接口。
     */
    private static final class OffsetComparator implements Comparator<ObjectMemoryCalculationInfo> {
        private OffsetComparator() {
        }

        /**
         * 比较两个对象的偏移量。
         *
         * @param o1 对象1
         * @param o2 对象2
         * @return 偏移量的差值
         */
        public int compare(ObjectMemoryCalculationInfo o1, ObjectMemoryCalculationInfo o2) {
            return o1.objectOffset - o2.objectOffset;
        }
    }
}
import sun.misc.Unsafe;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;

/**
 * ObjectMemoryCalculator 是一个用于计算 Java 对象内存占用情况的工具类。它利用反射和 `sun.misc.Unsafe` 类,
 * 能够递归遍历对象及其内部字段,计算出对象的浅层大小、数组大小、偏移量以及索引比例等信息。
 * 主要功能包括:
 * - 计算单个对象的内存占用(包括基本类型和引用类型)
 * - 处理对象数组及其内容的内存占用
 * - 遍历对象树结构,处理嵌套对象和字段
 * - 防止递归循环引用导致的无限循环
 * - 支持指定最大递归深度限制
 * @author HH287 <br>
 * @version 1.0.0 2024/4/8 17:25 <br>
 * @since JDK 1.8.0
 */
public class ObjectMemoryCalculator {
    private static final Unsafe unsafe;
    private static final Map<Class, Integer> primitiveSizes;
    private static final int objectRefSize;
    private IdentityHashMap<Object, Boolean> visitedObjects = new IdentityHashMap(100);

    /**
     * 构造函数。
     */
    public ObjectMemoryCalculator() {
    }


    /**
     * 递归计算对象的内存占用。
     *
     * @param obj       当前要计算内存的对象。
     * @param field     当前对象的字段,用于计算递归深度和字段信息。
     * @param depth     当前递归深度。
     * @return 对象内存占用的详细信息。
     * @throws IllegalAccessException 如果访问对象的字段时发生错误。
     */
    private ObjectMemoryCalculationInfo calculateMemory(Object obj, Field field, int depth) throws IllegalAccessException {
        boolean isPrimitive = field != null && field.getType().isPrimitive(); // 判断字段是否为基本类型
        boolean isRecursive = false;
        if (!isPrimitive) {
            if (this.visitedObjects.containsKey(obj)) {
                isRecursive = true; // 检测循环引用
            }

            this.visitedObjects.put(obj, true);
        }

        Class type = field != null && (obj == null || isPrimitive) ? field.getType() : obj.getClass(); // 获取对象或字段类型
        int arraySize = 0;
        int baseOffset = 0;
        int indexScale = 0;
        if (type.isArray() && obj != null) { // 处理数组类型
            baseOffset = unsafe.arrayBaseOffset(type);
            indexScale = unsafe.arrayIndexScale(type);
            arraySize = baseOffset + indexScale * Array.getLength(obj);
        }

        ObjectMemoryCalculationInfo root;
        int nextDepth;
        if (field == null) {
            root = new ObjectMemoryCalculationInfo("", type.getCanonicalName(), getContents(obj, type), 0, getShallowSize(type), arraySize, baseOffset, indexScale);
        } else {
            nextDepth = (int)unsafe.objectFieldOffset(field);
            root = new ObjectMemoryCalculationInfo(field.getName(), type.getCanonicalName(), getContents(obj, type), nextDepth, getShallowSize(type), arraySize, baseOffset, indexScale);
        }

        nextDepth = depth + 1;
        if (!isRecursive && obj != null && nextDepth < 10 && !type.getName().startsWith("sun.")) { // 避免递归过深和遍历Java内部类
            if (isObjectArray(type)) { // 处理对象数组
                Object[] array = (Object[])((Object[])obj);
                Object[] tempArray = array;
                int length = array.length;

                for(int i = 0; i < length; ++i) {
                    Object item = tempArray[i];
                    if (item != null) {
                        root.addChild(this.calculateMemory(item, (Field)null, nextDepth));
                    }
                }
            } else {
                // 遍历并计算对象所有非静态字段的内存占用
                Iterator iterator = getAllFields(type).iterator();

                while(iterator.hasNext()) {
                    Field tempField = (Field)iterator.next();
                    if ((tempField.getModifiers() & 8) == 0) {
                        tempField.setAccessible(true);
                        root.addChild(this.calculateMemory(tempField.get(obj), tempField, nextDepth));
                    }
                }
            }
        }

        root.sortChildrenByOffset(); // 根据字段偏移量排序子对象
        return root;
    }

    /**
     * 获取类的所有字段,包括继承自父类的字段。
     *
     * @param type 要获取字段的类。
     * @return 类中所有字段的列表。
     */
    private static List<Field> getAllFields(Class type) {
        if (type.isPrimitive()) {
            return Collections.emptyList();
        } else {
            Class current = type;
            List<Field> result = new ArrayList(10);

            while(true) {
                Collections.addAll(result, current.getDeclaredFields());
                if (current == Object.class) {
                    return result;
                }

                current = current.getSuperclass();
            }
        }
    }

    /**
     * 判断给定类型是否为对象数组。
     *
     * @param type 类型。
     * @return 如果给定类型是对象数组,则返回true,否则返回false。
     */
    private static boolean isObjectArray(Class type) {
        if (!type.isArray()) {
            return false;
        } else {
            return type != byte[].class && type != boolean[].class && type != char[].class && type != short[].class && type != int[].class && type != long[].class && type != float[].class && type != double[].class;
        }
    }

    /**
     * 根据对象值和类型,返回对象内容的字符串表示。
     *
     * @param value 对象值。
     * @param type  对象类型。
     * @return 对象内容的字符串表示。
     */
    private static String getContents(Object value, Class type) {
        if (value == null) {
            return "null";
        } else if (type.isArray()) {
            // 根据不同的数组类型,返回不同的字符串表示
            if (type == byte[].class) {
                return Arrays.toString((byte[])((byte[])value));
            } else if (type == boolean[].class) {
                return Arrays.toString((boolean[])((boolean[])value));
            } else if (type == char[].class) {
                return Arrays.toString((char[])((char[])value));
            } else if (type == short[].class) {
                return Arrays.toString((short[])((short[])value));
            } else if (type == int[].class) {
                return Arrays.toString((int[])((int[])value));
            } else if (type == long[].class) {
                return Arrays.toString((long[])((long[])value));
            } else if (type == float[].class) {
                return Arrays.toString((float[])((float[])value));
            } else {
                return type == double[].class ? Arrays.toString((double[])((double[])value)) : Arrays.toString((Object[])((Object[])value));
            }
        } else {
            return value.toString();
        }
    }

    /**
     * 获取给定类型的浅层大小,即对象引用大小。
     *
     * @param type 类型。
     * @return 给定类型的浅层大小。
     */
    private static int getShallowSize(Class type) {
        if (type.isPrimitive()) {
            Integer result = (Integer)primitiveSizes.get(type);
            return result != null ? result : 0;
        } else {
            return objectRefSize;
        }
    }

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe)field.get((Object)null);
            objectRefSize = unsafe.arrayIndexScale(Object[].class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        // 初始化基本类型大小映射
        primitiveSizes = new HashMap(10);
        primitiveSizes.put(Byte.TYPE, 1);
        primitiveSizes.put(Character.TYPE, 2);
        primitiveSizes.put(Integer.TYPE, 4);
        primitiveSizes.put(Long.TYPE, 8);
        primitiveSizes.put(Float.TYPE, 4);
        primitiveSizes.put(Double.TYPE, 8);
        primitiveSizes.put(Boolean.TYPE, 1);
    }

    /**
     * 计算给定对象的内存占用。
     *
     * @param obj 要计算内存占用的对象。
     * @return 对象内存占用的详细信息。
     * @throws IllegalAccessException 如果访问对象的字段时发生错误。
     */
    public ObjectMemoryCalculationInfo calculateMemory(Object obj) throws IllegalAccessException {
        ObjectMemoryCalculationInfo result;
        try {
            result = this.calculateMemory(obj, (Field)null, 0);
        } finally {
            this.visitedObjects.clear(); // 清理已访问对象缓存
        }

        return result;
    }
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值