在一些监控中发现序列化后的对象实际没有特别大,但在JVM中占用的内存却很大,甚至导致了OOM(Out of Memory)。

写了一个简单的测试来计算对象在JVM中存储的大小。

源码

import com.au92.common.util.json.JsonUtils;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

/**
 * 计算对象内存大小的工具类
 *
 * @author p_x_c
 */
public class MemoryMeasurer {

    // 默认类型大小(单位:字节)
    private static final Map<Class<?>, Long> DEFAULT_SIZES = new HashMap<>();

    static {
        DEFAULT_SIZES.put(byte.class, 1L);
        DEFAULT_SIZES.put(short.class, 2L);
        DEFAULT_SIZES.put(int.class, 4L);
        DEFAULT_SIZES.put(long.class, 8L);
        DEFAULT_SIZES.put(float.class, 4L);
        DEFAULT_SIZES.put(double.class, 8L);
        DEFAULT_SIZES.put(char.class, 2L);
        DEFAULT_SIZES.put(boolean.class, 1L);

        DEFAULT_SIZES.put(Byte.class, 1L);
        DEFAULT_SIZES.put(Short.class, 2L);
        DEFAULT_SIZES.put(Integer.class, 4L);
        DEFAULT_SIZES.put(Long.class, 8L);
        DEFAULT_SIZES.put(Float.class, 4L);
        DEFAULT_SIZES.put(Double.class, 8L);
        DEFAULT_SIZES.put(Character.class, 2L);
        DEFAULT_SIZES.put(Boolean.class, 1L);
        DEFAULT_SIZES.put(String.class, 40L); // 默认初始大小,真实内容另计
    }

    /**
     * 估算对象的内存大小(递归)
     */
    public static long sizeOf(Object obj) {
        return sizeOf(obj, new IdentityHashMap<>());
    }

    private static long sizeOf(Object obj, IdentityHashMap<Object, Boolean> visited) {
        if (obj == null || visited.containsKey(obj)) {
            return 0L;
        }

        visited.put(obj, true);

        Class<?> clazz = obj.getClass();

        // 基本类型包装类或常用类型(直接估算)
        if (DEFAULT_SIZES.containsKey(clazz)) {
            return DEFAULT_SIZES.get(clazz);
        }

        // 字符串特殊处理
        if (obj instanceof String str) {
            return 40L + str.length() * 2L; // header + char[] UTF-16
        }

        // 数组
        if (clazz.isArray()) {
            int len = Array.getLength(obj);
            long size = 16L; // array object header
            for (int i = 0; i < len; i++) {
                size += sizeOf(Array.get(obj, i), visited);
            }
            return size;
        }

        // 集合
        if (obj instanceof Collection<?> coll) {
            long size = 24L; // collection object header
            for (Object item : coll) {
                size += sizeOf(item, visited);
            }
            return size;
        }

        // Map
        if (obj instanceof Map<?, ?> map) {
            long size = 32L; // map object header
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                size += sizeOf(entry.getKey(), visited);
                size += sizeOf(entry.getValue(), visited);
            }
            return size;
        }

        // 普通对象:递归所有字段
        long totalSize = 16L; // object header
        for (Field field : getAllFields(clazz)) {
            field.setAccessible(true);
            try {
                Object fieldValue = field.get(obj);
                totalSize += sizeOf(fieldValue, visited);
            } catch (IllegalAccessException ignored) {
            }
        }

        return totalSize;
    }

    /**
     * 获取所有字段(包括父类)
     */
    private static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null && clazz != Object.class) {
            Collections.addAll(fields, clazz.getDeclaredFields());
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

}

测试代码

    public static class Test {
        public Test(int id) {
        }
    }

    public static void main(String[] args) {
        Map<String, Object> data = new HashMap<>();
        data.put("id", 123);
        data.put("name", "测试用户");
        data.put("active", true);
        data.put("tags", Arrays.asList("a", "b", "c", "d", "e"));
        data.put("numbers", Arrays.asList(1, 2, 3, 4));
        data.put("testObject", new Test(3));

        long size = MemoryMeasurer.sizeOf(data);
        System.out.println("估算JVM内存大小:" + size + " bytes");
        String json = JsonUtils.toJSONString(data);
        byte[] bytesx = json.getBytes(StandardCharsets.UTF_8);
        System.out.println("实际传输数据 字节大小: " + bytesx.length + " bytes");
    }