实现一个sizeof获取Java对象大小 博客分类: 代码琐记 Java对象大小内存sizeofhotspot
程序员文章站
2024-03-18 19:42:22
...
由于Java的设计者不想让程序员管理和了解内存的使用,我们想要知道一个对象在内存中的大小变得比较困难了。本文提供了可以获取对象的大小的方法,但是由于各个虚拟机在内存使用上可能存在不同,因此该方法不能在各虚拟机上都适用,而是仅在hotspot 32位虚拟机上,或者其它内存管理方式与hotspot 32位虚拟机相同的虚拟机上 适用。
本方法使用了Unsafe类来访问对象的私有属性,因此有些特殊的设置和做法,要留意类定义前面的文字说明。
要想计算对象大小,我们必须熟悉hotspot32上不同类型所占的空间:
一,原始类型primitives:
boolean:1 byte,尽管Java语言规范里面boolean是一个bit;
byte:1 byte;
char:2 bytes;
short:2 bytes;
int:4 bytes;
float:4 bytes;
long:8 bytes;
double:8 bytes。
二,引用类型:
4 bytes,即使是null值也是如此。
三,空的普通对象(无任何属性,如new Object(),不是null对象):
8 bytes。存放对象头的各种信息。
四,空的数组(即长度为0的数组,而不是null数组):
12 bytes,其中比普通对象多出来的4 bytes是用来放数组长度的。
五,hotspot 32分配内存是以8 bytes的整数倍来计算的,因此不足8个字节的对象要补足剩余的字节数以对齐。
/** * 这个例子在eclipse里不能直接编译,要到项目的属性, * Java Compiler,Errors/Warnings中Deprecated and restricted API * 中Forbidden reference(access rules)中设置为warning。 * * 获取一个Java对象在内存所占的空间,不同的虚拟机内存管理方式可能不同, * 本例是针对32位的hotspot虚拟机的。 * * 由于虚拟机对字符串做了特殊处理,比如将其放入常量池,因此sizeof得到的字符串 * 包含了常量池里面占用的空间。基本类型的包装类也会重复利用对象。 * * 设计作者: teasp * 信息描述: */ @SuppressWarnings("restriction") public class HotspotSizeof { public static final int OBJ_BASIC_LEN = 8 * 8; public static final int ARRAY_BASIC_LEN = 12 * 8; public static final int OBJ_REF_LEN = 4 * 8; public static final int ALIGN = 8 * 8; private static Unsafe UNSAFE; static { try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); UNSAFE = (Unsafe) theUnsafe.get(null); } catch (Exception e) { e.printStackTrace(); } } /** * 原始类型的种类,以及每个类型所占空间,单位为bit * @author Administrator * */ private enum PType { 布尔(8)/*Java语言规定是1个bit*/,字节(8),字符(16),短整(16), 整形(32),浮点(32),长整(64),双精(64); private int bits; private PType(int bits) { this.bits = bits; } public int getBits() { return bits; } } /** * 计算obj对象在虚拟机中所占的内存,单位为bit。 * 如果isPapa为true,则表明计算的是obj对象父类定义的属性。 * * @param obj * @param clazz * @param isPapa * @return */ private static int getObjBits(Object obj, Class<?> clazz, boolean isPapa) { int bits = 0; if (obj == null) { return bits; } bits += OBJ_BASIC_LEN; if (isPapa) { bits = 0; } Field[] fields = clazz.getDeclaredFields(); if (fields != null) { for (Field field : fields) { //静态属性不参与计算 if (Modifier.isStatic(field.getModifiers())) { // System.out.println("static " + field.getName()); continue; } Class<?> c = field.getType(); if (c == boolean.class) { bits += PType.布尔.getBits(); } else if (c == byte.class) { bits += PType.字节.getBits(); } else if (c == char.class) { bits += PType.字符.getBits(); } else if (c == short.class) { bits += PType.短整.getBits(); } else if (c == int.class) { bits += PType.整形.getBits(); System.out.println(field.getName() + "=" + UNSAFE.getInt(obj, UNSAFE.objectFieldOffset(field))); } else if (c == float.class) { bits += PType.浮点.getBits(); } else if (c == long.class) { bits += PType.长整.getBits(); } else if (c == double.class) { bits += PType.双精.getBits(); } else if (c == void.class) { //nothing } else if (c.isArray()) {//是数组 Object o = UNSAFE.getObject(obj, UNSAFE.objectFieldOffset(field)); bits += OBJ_REF_LEN; if (o != null) { try { bits += bitsofArray(o); } catch (Exception e) { throw new RuntimeException(e); } } } else {//普通对象 Object o = UNSAFE.getObject(obj, UNSAFE.objectFieldOffset(field)); bits += OBJ_REF_LEN; if (o != null) { try { bits += bitsof(o); } catch (Exception e) { throw new RuntimeException(e); } } } } } Class<?> papa = clazz.getSuperclass(); if (papa != null) { bits += getObjBits(obj, papa, true); } //补齐,当计算父类属性时,因为是对同一个对象在进行统计,所以不要补齐。 //一个对象只做一次补齐动作。 if (false == isPapa) { if (bits%ALIGN > 0) { bits += (ALIGN - bits%ALIGN); } } return bits; } /** * 计算arr数组在虚拟机中所占的内存,单位为bit * @param arr * @return */ private static int bitsofArray(Object arr) { int bits = 0; if (arr == null) { return bits; } bits += ARRAY_BASIC_LEN; Class<?> c = arr.getClass(); if (c.isArray() == false) { throw new RuntimeException("Must be array!"); } if (c == boolean[].class) { bits += PType.布尔.getBits() * ((boolean[])arr).length; } else if (c == byte[].class) { bits += PType.字节.getBits() * ((byte[])arr).length; } else if (c == char[].class) { bits += PType.字符.getBits() * ((char[])arr).length; } else if (c == short[].class) { bits += PType.短整.getBits() * ((short[])arr).length; } else if (c == int[].class) { bits += PType.整形.getBits() * ((int[])arr).length; } else if (c == float[].class) { bits += PType.浮点.getBits() * ((float[])arr).length; } else if (c == long[].class) { bits += PType.长整.getBits() * ((long[])arr).length; } else if (c == double[].class) { bits += PType.双精.getBits() * ((double[])arr).length; } else { Object[] os = (Object[])arr; for (Object o : os) { bits += OBJ_REF_LEN + bitsof(o); } } //补齐 if (bits%ALIGN > 0) { bits += (ALIGN - bits%ALIGN); } return bits; } /** * 计算obj对象在虚拟机中所占的内存,单位为bit * @param obj * @return */ private static int bitsof(Object obj) { if (obj == null) { return 0; } if (obj.getClass().isArray()) { return bitsofArray(obj); } return getObjBits(obj, obj.getClass(), false); } /** * 计算obj对象在虚拟机中所占的内存,单位为byte * @param obj * @return */ public static int sizeof(Object obj) { return bitsof(obj)/8; } private static void runGC() throws Exception { // It helps to call Runtime.gc() // using several method calls: for (int r=0; r<4; ++r) _runGC(); } private static void _runGC() throws Exception { long usedMem1 = usedMemory(), usedMem2 = Long.MAX_VALUE; for (int i=0; (usedMem1<usedMem2) && (i<500); ++i) { Runtime.getRuntime().runFinalization(); Runtime.getRuntime().gc(); Thread.yield(); usedMem2 = usedMem1; usedMem1 = usedMemory(); } } private static long usedMemory() { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } /** * 本方法在计算String以及原始类型的包装类的时候可能不准。 * String s = "abc"; 这种方式产生的String对象会被放入常量池。 * Integer.valueOf(1)会返回缓存的对象而不是new一个。 * @param cls * @return * @throws Exception */ private static long determinObjSize(Class<?> cls) throws Exception { // Warm up all classes/methods we will use runGC(); usedMemory(); // Array to keep strong references to allocated objects final int count = 100000; Object[] objects = new Object[count]; long heap1 = 0; // Allocate count+1 objects, discard the first one for (int i = -1; i < count; ++i) { Object object = null; // Instantiate your data here and assign it to object // object = new Object(); //object = new Integer(i); //object = new Long(i); //object = new String(); //object = new byte[128][1] object = cls.newInstance(); if (i >= 0) objects [i] = object; else { object = null; // Discard the warm up object runGC(); heap1 = usedMemory(); // Take a before heap snapshot } } runGC(); long heap2 = usedMemory(); // Take an after heap snapshot: final int size = Math.round(((float)(heap2 - heap1))/count); System.out.println("'before' heap: " + heap1 + ", 'after' heap: " + heap2); System.out.println("heap delta: " + (heap2 - heap1) + ", {" + objects [0].getClass () + "} size = " + size + " bytes"); for (int i = 0; i < count; ++i) objects [i] = null; objects = null; return size; } public static void main(String[] args) { HotspotSizeof hs = new HotspotSizeof(); hs.test(); } @Test public void test() { try { Assert.assertEquals(determinObjSize(Obj4SizeofTest.class), sizeof(new Obj4SizeofTest())); } catch (Exception e) { throw new RuntimeException(e); } } }
测试用的两个类:
/** * 设计作者: teasp * 信息描述: */ public class Papa { @SuppressWarnings("unused") private int aint = 4; public static int bint; @SuppressWarnings("unused") // private String str = "123"; // private String str = new String("123"); // private String str = new String(new byte[]{49,50,51}); private String str = new String(new char[]{49,50,51}); @SuppressWarnings("unused") private int[] ints = {}; @SuppressWarnings("unused") private int[][] intss = {{}}; // private int[][] intss = {{1},{1,2}}; } /** * 设计作者: teasp * 信息描述: */ public class Obj4SizeofTest extends Papa { @SuppressWarnings("unused") private int aint = 3; public int bint = 4; @SuppressWarnings("unused") private boolean b1 = true; @SuppressWarnings("unused") private boolean b2 = true; @SuppressWarnings("unused") private boolean b3 = true; @SuppressWarnings("unused") private boolean b4 = true; @SuppressWarnings("unused") private boolean b5 = true; @SuppressWarnings("unused") private boolean b6 = true; @SuppressWarnings("unused") private boolean b7 = true; @SuppressWarnings("unused") private boolean b8 = true; @SuppressWarnings("unused") private String str1; @SuppressWarnings("unused") private Object obj = new Papa(); public static final byte[] bytes = {97}; @SuppressWarnings("unused") private String str2 = new String(bytes); @SuppressWarnings("unused") private Integer i = new Integer(1); @SuppressWarnings("unused") private int[] is = {1,2,3}; @SuppressWarnings("unused") private Object[][] objs = {{new Object(),new Object()},{new Object(),new Object()}}; }