最近这段时间在看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是《深入对象大小》这篇博文中博主的总结,大家可以下载来看看,一定会有收获的哦。
相关推荐
总结来说,理解Java对象大小的计算对于优化内存使用、防止内存泄漏以及提高应用程序性能至关重要。开发人员需要了解不同方法的优缺点,并根据实际需求选择合适的方式。同时,应当注意,过于关注单个对象的大小可能...
总的来说,通过`sizeOf`计算Java对象的大小是一项复杂的工作,涉及到JVM内部的内存管理和对象表示。尽管Java API没有直接提供这样的功能,但我们可以借助第三方库或自定义工具来实现。理解对象大小对于优化内存使用...
本篇文章将深入探讨如何计算Java对象占用的内存字节数,以及影响这一数值的因素。 首先,Java对象在堆内存中由四个部分组成:对象头(A)、基本类型域(B)、引用类型域(C)和填充物(D)。 **对象头(A)**: ...
本文将深入探讨如何计算Java对象所占内存,并通过提供的代码示例进行详细解析。 首先,我们需要理解Java对象内存占用的基本原理。每个Java对象都由三部分组成:对象头(Object Header)、实例数据(Instance Data)...
本示例“测量Java对象大小的demo”提供了一种方法,通过使用Java的`java.lang.instrument`包来注入`javaagent`,进而利用`Instrumentation`接口测量Java对象的精确内存占用。下面我们将详细探讨这一过程。 首先,`...
- Java的`java.lang.instrument.Instrumentation`接口提供了获取对象大小的方法。通过代理类或者预加载器(如JDK动态代理或ByteBuddy),可以在运行时获取对象大小。 4. **Memory Profiler**:如YourKit、Eclipse ...
例如,`sun.misc.Unsafe`类(虽然非标准,但广泛使用)提供了访问内存的能力,包括获取对象大小。然而,需要注意的是,这个类在Java 9及以后的版本中被弃用,并且在某些JRE中可能不可用。 `SizeOfObject.java`可能...
本文将深入探讨Java中的对象内存占用,以及如何使用"java-sizeof-0.0.4"工具来查看Java对象在内存中的大小。 在Java中,内存主要分为堆内存(Heap)和栈内存(Stack)。对象通常存储在堆内存中,而基本类型的变量和...
此外,Objects类(在Java 7引入)提供了equals、hashCode和requireNonNull等方法,增强了对象比较和非空检查的功能。 数据处理工具类往往涉及集合操作。Java的util包包含了多种集合框架类,如List、Set、Map等。...
总之,Java对象和数组的内存占用取决于JVM的配置、对象的字段、数组的元素类型和长度等因素。开启或关闭压缩选项会影响对象和数组引用的大小,进而影响整体的内存占用。对于内存优化和性能调优来说,了解这些细节至...
接下来,我们将详细介绍如何精确地计算Java字符串所占用的内存空间。这里主要关注字符串对象本身的内存占用情况,而非其引用部分。 ##### Java字符串结构分析 在Java中,字符串(String)是一个不可变的对象,它主要...
然后,我们可以定义一个方法来计算交集和并集的大小,并用这些信息来计算杰卡德相似度。 ```java public class Vector { private Map, Double> elements; // 构造函数、getters、setters等... public double ...
Java对象缓冲实现中间件是一种高效的数据存储和访问机制,它通过在内存中创建一个对象池来减少对硬盘或网络资源的频繁访问,从而提高应用程序的性能。这种中间件设计的核心理念是利用内存的高速访问特性,将常用或者...
计算机后端-Java-Java核心基础-第15章 面向对象07 21. 接口练习:比较对象大小.avi
4. **深/浅拷贝**:在计算对象大小时,需要区分深拷贝和浅拷贝。浅拷贝只复制对象本身,不包括引用的对象;深拷贝则会递归复制所有引用的对象,因此深拷贝的内存占用通常大于浅拷贝。 5. **垃圾收集与内存管理**:...
本知识点主要探讨如何在Java中实现自适应图片大小的水印功能,以及遇到图片过小时的处理策略。 首先,我们需要理解水印的基本概念。水印是在图像或文档上添加的一种视觉标记,它可以是文字、图像或者图形,用于表明...
本项目专注于一个特定的需求,即“Java提取EXCEL图片重命名并改变像素大小”。这个功能对于那些需要批量处理Excel文件中的图像,比如统一调整格式、优化存储大小或适应不同展示环境的场景非常有用。下面我们将详细...
- Java使用数组来存储固定大小的同类型元素序列,需要指定数组的类型和大小。 - 掌握基本数据类型、运算符和表达式,是编写有效Java程序的基础。 4. Java程序设计基础 - Java程序设计涉及顺序结构、选择结构(if...