最近这段时间在看Ehcache的源码,突然想起如何估算内存中的缓存空间的大小呢,缓存空间 = ∑ object_1 + object_2 + ... + object_N ,就必须计算每个缓存对象的大小。我们知道java内存分为两种,堆内存和栈内存,栈是Java线程运行的独立空间,而堆内存是多线程运行的公共空间。堆是java对象实际存储的地点。那么计算对象大小就是计算对象在堆中占用内存空间的大小。
于是在互联网上收集了几种计算策略,在本文中作简要的归纳和总结。一种就是通过java对象序列化的方式,用序列化后字节流的大小来粗略描述java对象大小;一种是通过Java 虚拟机工具接口(Java Virtual Machine Tool Interface,JVMTI)里面的Instrumentation对象中提供的getObjectSize方法;最后一种通过创建大量的对象,然后通过计算堆内存的的使用情况,然后通过取平均值的方式计算对象大小。下面将给出几种计算方式的源码。
先给出基类AbstractTestCase.java,基类中完成待测试对象的初始化。
import java.util.HashMap;
import java.util.Map;
public abstract class AbstractTestCase {
//the count method's name
private String name = null;
public static final Object aNullObject = new Object();
public static final byte aByte = (byte)255;
public static final char aChar = 'a';
public static final char aChineseChar = '中';
public static final short aBufferedShort = 0;
public static final short aUnBufferedShort = (short)65535;
public static final int aBufferedInt = 0;
public static final int aUnBufferedInt = 100000;
public static final long aLong = 100000000L;
public static final float aFloat = 5000.0f;
public static final double aDouble = 109.21d;
public static final String aNullString = null;
public static final String aConstantString = "0123456789";
public static final String aNewConstructString = new String("9876543210");
public static final Object[] aNullObjectArray = new Object[0];
public static final Object[] aOneObjectArray = new Object[1];
public static final Object[] aTwoObjectArray = new Object[2];
public static final Object[] aThreeObjectArray = new Object[3];
public static final Object[][] aNullTwoDimensionObjectArray = new Object[0][0];
public static final byte[][] aBigByteArray = new byte[8*1024][8*1024];
//result Map
private static final Map<String,Long> result = new HashMap<String,Long>(0);
private static final Map<String,Object> data = new HashMap<String,Object>(0);
public static final String resultString = "the 【%s】 object use 【%d】 bytes memory size ";
static {
data.put("aNullObject", aNullObject);
data.put("aByte", aByte);
data.put("aChar", aChar);
data.put("aChineseChar", aChineseChar);
data.put("aBufferedShort", aBufferedShort);
data.put("aUnBufferedShort", aUnBufferedShort);
data.put("aBufferedInt", aBufferedInt);
data.put("aUnBufferedInt", aUnBufferedInt);
data.put("aLong", aLong);
data.put("aFloat", aFloat);
data.put("aDouble", aDouble);
data.put("aNullString", aNullString);
data.put("aConstantString", aConstantString);
data.put("aNewConstructString", aNewConstructString);
data.put("aNullObjectArray", aNullObjectArray);
data.put("aOneObjectArray", aOneObjectArray);
data.put("aTwoObjectArray", aTwoObjectArray);
data.put("aThreeObjectArray", aThreeObjectArray);
data.put("aNullTwoDimensionObjectArray", aNullTwoDimensionObjectArray);
data.put("aBigByteArray", aBigByteArray);
}
public AbstractTestCase(String name){
this.name = name;
}
//实现策略
public abstract long countObjectSize(Object obj);
//测试用例
public void countTheResult(){
for(String testCaseName : data.keySet()){
Object obj = data.get(testCaseName);
Long count = this.countObjectSize(obj);
result.put(testCaseName, count);
}
}
//打印结果
public void printTheResult(){
System.out.println("the " + name + " count method run start.........................");
for(String testCaseName : result.keySet()){
System.out.println(String.format(resultString, testCaseName,result.get(testCaseName)));
}
System.out.println("the " + name + " count method run finished .........................");
System.out.println("~~ :)~");
}
}
1、使用Instrumentation对象的getObjectSize方法
import sizeof.agent.SizeOfAgent;
public class TestCaseUseJVMTI extends AbstractTestCase {
public TestCaseUseJVMTI() {
super("JVMTI");
}
@Override
public long countObjectSize(Object obj) {
return SizeOfAgent.fullSizeOf(obj);
}
}
运行过程中,需要下载sizeofag.jar,放在Eclipse工程目录/lib/下,添加到classpath,并添加虚拟机运行参数“-javaagent:lib/sizeofag.jar”。
2、使用java序列化的方式计算对象大小
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
/**
* @author thinkpad
*
*/
public class TestCaseUseSerialization extends AbstractTestCase{
public TestCaseUseSerialization() {
super("Serialization");
}
//重写Write方法,只计数,不输出
private class CountOutputStream extends OutputStream {
long count = 0;
@Override
public void write(int b) throws IOException {
count++;
}
}
@Override
public long countObjectSize(Object obj) {
if (obj == null)
return 0L;
if (!(obj instanceof Serializable)) {
return 0L;
//throw new IllegalArgumentException("没有实现序列化接口");
}
long result = 0L;
try{
CountOutputStream buffer = new CountOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buffer);
out.writeObject(obj);
out.close();
result = buffer.count;
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException();
}
return result;
}
}
3、国外一个兄弟写的一个程序计算对象大小 链接地址
public class Sizeof
{
public static void main (String [] args) throws Exception
{
//System.exit(-1);
// 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
//object = new Object();
//计算byte
//object = (byte)-129;
//计算char
//object = '0';
//计算中文char
//object = '中';
//计算short -128 到 127均已经缓存
//object = (short)0;
//计算int 0
//object = (int)0;
//计算int 超出缓存 10000 -127~128
//object = (int)10000;
//计算long 超出缓存
//object = 10000L;
//计算float
//object = 0f;
//计算double
//object = 0d;
object = new String("");
//计算长度为0的object数组
//object = Array.newInstance(Object.class, 0);
//计算长度为1的object数组
//object = Array.newInstance(Object.class, 1);
//计算长度为2的object数组
//object = Array.newInstance(Object.class, 2);
//计算长度为3的object数组
//object = Array.newInstance(Object.class, 3);
//object = Array.newInstance(Character.class, 0);
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;
}
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)
{
s_runtime.runFinalization ();
s_runtime.gc ();
Thread.currentThread ().yield ();
usedMem2 = usedMem1;
usedMem1 = usedMemory ();
}
}
private static long usedMemory ()
{
return s_runtime.totalMemory () - s_runtime.freeMemory ();
}
private static final Runtime s_runtime = Runtime.getRuntime ();
} // End of class
测试主方法:
public class TestMain {
/**
* @param args
*/
public static void main(String[] args) {
AbstractTestCase [] testes = {
new TestCaseUseJVMTI(),
new TestCaseUseSerialization()
};
for(AbstractTestCase item :testes){
item.countTheResult();
item.printTheResult();
}
}
}
对比和分析和对比,JVMTI是官方提供的计算Object大小的方式,应该算是比较好,但是官方的方法说明中也明确表示也是一种模糊的计算方式。
序列化的方式,应该是一定程度上反映Java对象内存的大小,但是序列化的方式对其影响很大,例如序列化过程中是否压缩,字符串的编码方式是否相同(JVM内部是用UTF-16来保存,而某种序列化方式采用UTF-8来保存)等等。
采用对象统计学求平均值的办法,从统计结果来看也是一种比较好的近似的方法。
参考文章:
老外的文章 http://www.jroller.com/maxim/entry/again_about_determining_size_of
大神的博客 http://rednaxelafx.iteye.com/blog/482058
iteye上讨论计算java对象大小话题 http://www.iteye.com/topic/580802
深入对象大小 http://www.iteye.com/topic/710998
讲解instrument的用法 http://blog.youkuaiyun.com/ykdsg/article/details/12080071
本文的附件中Dive in Java Object size.doc是《深入对象大小》这篇博文中博主的总结,大家可以下载来看看,一定会有收获的哦。