由于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()}};
}