java 对象实际占用内存大小预估工具类
返回对象占用预估字节数
返回字节数对应格式化后的字符串(xx Kb)
使用效果
依赖了lombock和hutool,项目不用这个可以去掉日志,稍微改写一下。
import cn.hutool.core.util.ClassUtil;
import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;/*** @author humorchen* date: 2024/4/1* description: 对象大小计算工具**/
@Slf4j
public class ObjectSizeUtil {/*** 嵌套深度不超过10层*/public static int MAX_DEPTH = 10;/*** 单位*/public static final String[] UNIT_NAMES = new String[]{"B", "KB", "MB", "GB", "TB", "PB", "EB"};/*** 字段缓存*/private static final Map<Class<?>, Field[]> FIELD_CACHE = new ConcurrentHashMap<>();/*** 获取对象大小,接近实际内存大小,不完全等于实际大小,用于快速计算内存大小* 性能测试:三层带List的对象,百万次调用,耗时 196ms** @param o* @return*/public static long estimateObjectSize(Object o) {return estimateObjectSize(o, 0);}/*** 获取对象大小,接近实际内存大小,不完全等于实际大小,用于快速计算内存大小* 性能测试:三层带List的对象,百万次调用,耗时 196ms** @param o* @param depth* @return*/private static long estimateObjectSize(Object o, int depth) {if (o == null) {return 4L;}// 深度兜底if (depth >= MAX_DEPTH) {return 4L;}// 基本类型、基本类型包装类、枚举、数组、集合、Map、注解、其他类Class<?> cls = o.getClass();// 64位机器上每个对象至少有16字节的开销long size = 16L;// 基本类和包装对象 int 的class是java.lang.Integerif (ClassUtil.isBasicType(cls)) {if (cls == Integer.class || cls == Float.class) {size += 4L;} else if (cls == Long.class || cls == Double.class) {size += 8L;} else if (cls == Short.class || cls == Byte.class) {size += 2L;} else if (cls == Character.class || cls == Boolean.class) {size += 1L;}} else if (cls == String.class) {// 字符串,每个字符2字节size += 2L * ((String) o).length();} else if (cls.isEnum()) {// 枚举为常量,内存占用视为一个对象引用地址4字节size = 4L;} else if (cls.isAnnotation()) {// 注解 内存占用视为一个对象引用地址4字节size = 4L;} else if (cls.isArray()) {// 数组 遍历数组中的每个元素,递归调用estimateObjectSize方法,数组里每个对象指针4字节int len = Array.getLength(o);for (int i = 0; i < len; i++) {size += estimateObjectSize(Array.get(o, i), depth + 1) + 4;}} else if (o instanceof Collection) {// 集合 遍历集合中的每个元素,递归调用estimateObjectSize方法Collection<?> collection = (Collection<?>) o;if (!collection.isEmpty()) {for (Object obj : collection) {// 集合里每个对象指针4字节size += estimateObjectSize(obj, depth + 1) + 4;}}} else if (o instanceof Map) {// Map 遍历Map中的每个元素,递归调用estimateObjectSize方法Map<?, ?> map = (Map<?, ?>) o;if (!map.isEmpty()) {for (Map.Entry<?, ?> entry : map.entrySet()) {// 集合里每个对象指针4字节 *2size += 8;size += estimateObjectSize(entry.getKey(), depth + 1);size += estimateObjectSize(entry.getValue(), depth + 1);}}} else {// 其他类,遍历类中的每个字段,递归调用estimateObjectSize方法,计算字段的内存占用,field获取每次执行都会拷贝,所以用缓存Field[] declaredFields = FIELD_CACHE.computeIfAbsent(cls, k -> {// 获取类声明的字段,会拷贝Field[]Field[] fields = getDeclaredFields(cls);// 设置可访问for (Field field : fields) {field.setAccessible(true);}return fields;});for (Field declaredField : declaredFields) {try {// 静态的字段直接算一个指针的内存if (Modifier.isStatic(declaredField.getModifiers())) {size += 4L;} else {// 当前字段的这个指针的内存占用加指向的对象内存占用Object object = declaredField.get(o);size += estimateObjectSize(object, depth + 1) + 4;}} catch (Exception e) {log.error("获取对象大小异常", e);}}}return size;}/*** 获取类声明的字段** @param cls* @return*/private static Field[] getDeclaredFields(Class<?> cls) {Set<Field> set = new LinkedHashSet<>();Class<?> currentClass = cls;int depth = 0;while (!Object.class.equals(currentClass) && depth < MAX_DEPTH) {set.addAll(Arrays.asList(currentClass.getDeclaredFields()));currentClass = currentClass.getSuperclass();depth++;}return set.toArray(new Field[0]);}/*** 格式化字节数** @param size* @return*/public static String formatSize(long size) {if (size <= 0) {return "0";}int digitGroups = Math.min(UNIT_NAMES.length - 1, (int) (Math.log10(size) / Math.log10(1024)));return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + UNIT_NAMES[digitGroups];}
}