Java计算一个对象占用内存的大小

在C/C++中计算某一个基本类型或者对象占用内存大小的方法很简单,只要调用库里面的sizeof()操作符即可,但是在Java的API里面并没有给我们提供类似的方法。那么我们可不可以自己实现一个Java中的sizeof()方法呢?答案是肯定的。为了计算一个Java对象占用内存的大小,首先你得对Java对象的内存结构有所了解。如果你还不了解,请先阅读Java内存结构。

首先介绍一下sun.misc.Unsafe类,该类是Java中很神奇的一个类,这个类是用于执行低级别、不安全操作的方法集合。尽管这个类和所有的方法都是公开的(public),但是这个类的使用仍然受限,你无法在自己Java程序中直接使用该类,因为它的构造函数是私有的(private)。但是我们仍然能获得它的实例,方法如下:

Unsafe unsafe;
try {
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        unsafe = (Unsafe)unsafeField.get(null);
    } catch (Throwable t) {
        unsafe = null;
    }
Unsafe类的更多介绍和用法可以参照 http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

我们将使用Unsafe类的如下方法来实现Java中的sizeof()

//获得对象中一个非静态字段的偏移量
public native long objectFieldOffset(java.lang.reflect.Field field);
//获得数组对象第一个元素的偏移量
public native int arrayBaseOffset(java.lang.Class aClass);
//获得数组每一个元素所占内存大小
public native int arrayIndexScale(java.lang.Class aClass);

sizeof()方法代码如下:

注意:不同的Java虚拟机,参数会各不一样,以下代码只适用于32位的HotSpot虚拟机。

package test;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import sun.misc.Unsafe;

public class JUtil {
	/**对象头部的大小 */
	private static final int OBJECT_HEADER_SIZE = 8;
	/**对象占用内存的最小值*/
	private static final int MINIMUM_OBJECT_SIZE = 8;
	/**对象按多少字节的粒度进行对齐*/
	private static final int OBJECT_ALIGNMENT = 8;
	
	public static long sizeOf(Object obj) {
		//获得Unsafe实例
		Unsafe unsafe;
        try {
            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            unsafe = (Unsafe)unsafeField.get(null);
        } catch (Throwable t) {
            unsafe = null;
        }
        
        //判断对象是否为数组
        if (obj.getClass().isArray()) {
            Class klazz = obj.getClass();
            int base = unsafe.arrayBaseOffset(klazz);
            int scale = unsafe.arrayIndexScale(klazz);
            long size = base + (scale * Array.getLength(obj));
            if ((size % OBJECT_ALIGNMENT) != 0) {
                size += OBJECT_ALIGNMENT - (size % OBJECT_ALIGNMENT);
            }
            return Math.max(MINIMUM_OBJECT_SIZE, size);
        } else {
        	//如果数组对象则迭代遍历该对象的父类,找到最后一个非静态字段的偏移量
            for (Class klazz = obj.getClass(); klazz != null; klazz = klazz.getSuperclass()) {
                long lastFieldOffset = -1;
                for (Field f : klazz.getDeclaredFields()) {
                    if (!Modifier.isStatic(f.getModifiers())) {
                        lastFieldOffset = Math.max(lastFieldOffset, unsafe.objectFieldOffset(f));
                    }
                }
                if (lastFieldOffset > 0) {
                    lastFieldOffset += 1;
                    if ((lastFieldOffset % OBJECT_ALIGNMENT) != 0) {
                        lastFieldOffset += OBJECT_ALIGNMENT - (lastFieldOffset % OBJECT_ALIGNMENT);
                    }
                    return Math.max(MINIMUM_OBJECT_SIZE, lastFieldOffset);
                }
            }
            //该对象没有任何属性
            long size = OBJECT_HEADER_SIZE;
            if ((size % OBJECT_ALIGNMENT) != 0) {
                size += OBJECT_ALIGNMENT - (size % OBJECT_ALIGNMENT);
            }
            return Math.max(MINIMUM_OBJECT_SIZE, size);
        }
    }
	
	public static void main(String[] args) throws InterruptedException {
		System.out.println(JUtil.sizeOf(new MyClass()));//输出32
		Thread.sleep(100000);//阻塞线程,为了使用jmap工具
	}
}
下面测试所用的类:

package test;

public class MyClass {
	private byte a;
	private int c;
	private boolean d;
	private long e;
	private Object f; 
}
最后我们使用jmap工具来检查计算的结果是否正确


上图中的各项含义分别是:类的编号,实例的个数,占用内存的大小,类的名字。

可以看出sizeof()方法计算出来的结果是完全正确的。

你可能感兴趣的:(Java)