最近这段时间在看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.csdn.net/ykdsg/article/details/12080071
本文的附件中Dive in Java Object size.doc是《深入对象大小》这篇博文中博主的总结,大家可以下载来看看,一定会有收获的哦。
相关推荐
NULL 博文链接:https://sdfx.iteye.com/blog/2201116
主要介绍了计算一个Java对象占用字节数的方法,较为详细的分析了Java中各类对象所占用的字节数,需要的朋友可以参考下
注入javaagent使用Instrumentation测量Java对象的大小
NULL 博文链接:https://312256159-qq-com.iteye.com/blog/1582196
JAVA对象所占内存大小计算例子,博文地址:http://blog.csdn.net/u012787710/article/details/53164226
有效测量出 java 对象 内存 大小 可供缓存等计算
统计缓存大小(查看java对象所占的内存大小).
计算机后端-Java-Java核心基础-第15章 面向对象07 21. 接口练习:比较对象大小.avi
命令行: java -XX:+PrintCommandLineFlags -version 查看jvm默认参数 分别是 -XX:+UseCompressedOops 和 -XX:+UseCompressedClassPointers 这2个参数都是默认开启(+代表开启,-代表关闭) UseCompressedOops:普通...
Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...
面向对象计算始于这个基本概念,20. 即现实世界可以被描绘成一系列完全自治、封装的对象,21. 这些对象通过一个受保护的接口访问其他对象。 22. 多态性:多态性是指23. 允许不同24. 类的对象对同25. 一消息作出响应...
这一约束限制了程序的灵活性,所以虽然某些java数据存储于堆栈中——特别是对象引用,但是java对象并不存储其中。 3) 堆 一种通用的内存池(也位于RAM区),用于存放所有的java对象。堆不同于堆栈的好处是:编译器...
Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...
Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...
Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...
Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...
在Java中没有sizeof运算符,所以没办法知道一个对象到底占用了多大的空间,但是在分配对象的时候会有一些基本的规则,我们根据这些规则大致能判断出来对象大小,需要的朋友可以参考下
Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...