老早之前写过一篇博客,是关于一个Integer对象到底占用多少字节的,现在看来,那篇文章竟然计算错了。这次再去计算,是因为之前写的一篇关于字长的文章里,看到了hotspot jvm里,对象占用空间是8字节对齐的,再加上之前关于字节那文章里带着一点-XX:+UseCompressedOops压缩指针参数的疑问,重新探究了下一个对象到底占用多少字节,以及如何计算它占用空间的方法。主要是参考了这篇很久以前的文章,不过试验了一把,instrumentation这种方法还是靠谱的。
import java.lang.instrument.Instrumentation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; /** * 对象占用字节大小工具类 * * @author tianmai.fh * @date 2014-03-18 11:29 */ public class SizeOfObject { static Instrumentation inst; public static void premain(String args, Instrumentation instP) { inst = instP; } /** * 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br> * 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br> * 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br> * * @param obj * @return */ public static long sizeOf(Object obj) { return inst.getObjectSize(obj); } /** * 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小 * * @param objP * @return * @throws IllegalAccessException */ public static long fullSizeOf(Object objP) throws IllegalAccessException { Set<Object> visited = new HashSet<Object>(); Deque<Object> toBeQueue = new ArrayDeque<>(); toBeQueue.add(objP); long size = 0L; while (toBeQueue.size() > 0) { Object obj = toBeQueue.poll(); //sizeOf的时候已经计基本类型和引用的长度,包括数组 size += skipObject(visited, obj) ? 0L : sizeOf(obj); Class<?> tmpObjClass = obj.getClass(); if (tmpObjClass.isArray()) { //[I , [F 基本类型名字长度是2 if (tmpObjClass.getName().length() > 2) { for (int i = 0, len = Array.getLength(obj); i < len; i++) { Object tmp = Array.get(obj, i); if (tmp != null) { //非基本类型需要深度遍历其对象 toBeQueue.add(Array.get(obj, i)); } } } } else { while (tmpObjClass != null) { Field[] fields = tmpObjClass.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) //静态不计 || field.getType().isPrimitive()) { //基本类型不重复计 continue; } field.setAccessible(true); Object fieldValue = field.get(obj); if (fieldValue == null) { continue; } toBeQueue.add(fieldValue); } tmpObjClass = tmpObjClass.getSuperclass(); } } } return size; } /** * String.intern的对象不计;计算过的不计,也避免死循环 * * @param visited * @param obj * @return */ static boolean skipObject(Set<Object> visited, Object obj) { if (obj instanceof String && obj == ((String) obj).intern()) { return true; } return visited.contains(obj); } }
跑代码前,需要按照那篇很老的文章先打包,这样才能注入Instrumentation实例,打包时候需要在MANIFEST.MF中写入三项值(注意包路径名改成自己的包名):
Premain-class: xxx.yyy.zzz.SizeOfObject Can-Redefine-Classes: false Boot-Class-Path:
来看看测试类:
import java.io.File; import static com.tmall.buy.structure.SizeOfObject.*; /** * @author tianmai.fh * @date 2014-03-18 20:17 */ public class SizeOfObjectTest { /** * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 = 16 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + padding/4 = 24 */ static class A { int a; } /** * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + 4 = 24 */ static class B { int a; int b; } /** * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 8 + 4 + padding/4 = 32 */ static class B2 { int b2a; Integer b2b; } /** * 不考虑对象头: * 4 + 4 + 4 * 3 + 3 * sizeOf(B) */ static class C extends A { int ba; B[] as = new B[3]; C() { for (int i = 0; i < as.length; i++) { as[i] = new B(); } } } static class D extends B { int da; Integer[] di = new Integer[3]; } /** * 会算上A的实例字段 */ static class E extends A { int ea; int eb; } public static void main(String[] args) throws IllegalAccessException { System.out.println(new File("./target/classes").getAbsolutePath()); System.out.println("sizeOf(new Object())=" + sizeOf(new Object())); System.out.println("sizeOf(new A())=" + sizeOf(new A())); System.out.println("sizeOf(new B())=" + sizeOf(new B())); System.out.println("sizeOf(new B2())=" + sizeOf(new B2())); System.out.println("sizeOf(new B[3])=" + sizeOf(new B[3])); System.out.println("sizeOf(new C())=" + sizeOf(new C())); System.out.println("fullSizeOf(new C())=" + fullSizeOf(new C())); System.out.println("sizeOf(new D())=" + sizeOf(new D())); System.out.println("fullSizeOf(new D())=" + fullSizeOf(new D())); System.out.println("sizeOf(new int[3])=" + sizeOf(new int[3])); System.out.println("sizeOf(new Integer(1)=" + sizeOf(new Integer(1))); System.out.println("sizeOf(new Integer[0])=" + sizeOf(new Integer[0])); System.out.println("sizeOf(new Integer[1])=" + sizeOf(new Integer[1])); System.out.println("sizeOf(new Integer[2])=" + sizeOf(new Integer[2])); System.out.println("sizeOf(new Integer[3])=" + sizeOf(new Integer[3])); System.out.println("sizeOf(new Integer[4])=" + sizeOf(new Integer[4])); System.out.println("sizeOf(new A[3])=" + sizeOf(new A[3])); System.out.println("sizeOf(new E())=" + sizeOf(new E())); } }
如果你是用maven打包的话,可以考虑在pom.xml文件中配置。打完jar包后,可以直接运行SizeOfObject了,但是要加上vm启动参数(test.jar是刚才打的jar包):
-javaagent:target/test.jar
在我64bit mac上,跑64位hotspot vm的结果如下,其中压缩对象指针参数是开启的,即-XX:+UseCompressedOops
sizeOf(new Object())=16 sizeOf(new A())=16 sizeOf(new B())=24 sizeOf(new B2())=24 sizeOf(new B[3])=32 sizeOf(new C())=24 fullSizeOf(new C())=128 sizeOf(new D())=32 fullSizeOf(new D())=64 sizeOf(new int[3])=32 sizeOf(new Integer(1)=16 sizeOf(new Integer[0])=16 sizeOf(new Integer[1])=24 sizeOf(new Integer[2])=24 sizeOf(new Integer[3])=32 sizeOf(new Integer[4])=32 sizeOf(new A[3])=32 sizeOf(new E())=24
如果关闭指针压缩,即在vm启动参数中加上-XX:-UseCompressedOops结果会不一样:
sizeOf(new Object())=16 sizeOf(new A())=24 sizeOf(new B())=24 sizeOf(new B2())=32 sizeOf(new B[3])=48 sizeOf(new C())=40 fullSizeOf(new C())=160 sizeOf(new D())=40 fullSizeOf(new D())=88 sizeOf(new int[3])=40 sizeOf(new Integer(1)=24 sizeOf(new Integer[0])=24 sizeOf(new Integer[1])=32 sizeOf(new Integer[2])=40 sizeOf(new Integer[3])=48 sizeOf(new Integer[4])=56 sizeOf(new A[3])=48 sizeOf(new E())=32
UseCompressOops开启和关闭,对对象头大小是有影响的,开启压缩,对象头是4+8=12byte;关闭压缩,对象头是8+8=16bytes。这个如何观察验证呢?
基于上述事实,通过new A()和new B()占用字节推断,基本类型int在开启、关闭压缩情况下都是占用4个bytes的,这个没有影响。而通过B和B2在开启、关闭指针压缩情况下的对比看,Integer类型分别占了4 bytes和8 bytes,实际上引用类型都是这样。如何验证?
new Integer[0]在压缩前后分别占用16、24个字节,这是又是为什么呢?
欲知后事,且听下回分解!enjoy it !
相关推荐
在Java编程语言中,了解一个对象占用的内存字节数对于优化内存使用和理解程序性能至关重要。本篇文章将深入探讨如何计算Java对象占用的内存字节数,以及影响这一数值的因素。 首先,Java对象在堆内存中由四个部分...
实验显示`Person`对象占用40字节,这是因为对象头的Klass Pointer从4字节增长到8字节,其他部分保持不变,总大小增加了8字节。 3. 数组的内存存储布局: 数组的内存占用除了包含对象头之外,还包括数组长度(4字节...
实例变量的大小取决于它们的数据类型,例如,一个int占4字节,一个double占8字节,一个引用占32位JVM的4字节或64位JVM的8字节。需要注意的是,基本类型的大小是固定的,但引用的大小会因是否开启压缩引用而变化。 3...
每种原始类型都对应着一个包装类(Wrapper Class),包装类是原始类型的对象表示形式。包装类提供了许多有用的方法,可以对原始类型进行操作。 在 Java 中,原始类型可以分为两大类:整数类型和浮点数类型。整数...
在Java编程语言中,了解一个对象的内存大小是很有用的,特别是在性能调优和内存管理方面。`sizeOf`通常是指用来计算对象在内存中占用空间的一种方法。本篇文章将探讨如何通过Java来实现这样的功能,以及相关知识点。...
这段代码首先创建了一个空的字节集,然后将一个字符串转换为字节集并赋值,最后调用“清空”命令清空字节集。这样,字节集就回到了无数据的状态。 清空字节集的效率对于处理大量数据的程序至关重要。易语言在设计时...
如果父类的最后一个属性与子类的第一个属性之间有不足4字节的空隙,会进行填充以满足4字节对齐。例如: ```java class A { byte a; } class B extends A { byte b; } ``` 这时,大小将是: - 头部(8字节) - ...
在Java编程语言中,了解一个对象占用的内存大小是非常重要的,尤其是在优化性能或者处理大量对象时。本示例主要探讨如何测试Java对象占用的内存大小,以便更好地理解内存使用情况。 首先,`SizeOf.java`可能是一个...
在Java编程语言中,了解一个对象占用的内存大小对于优化程序性能、理解内存消耗以及防止内存泄漏至关重要。本文将深入探讨如何计算Java对象所占内存,并通过提供的代码示例进行详细解析。 首先,我们需要理解Java...
"计算对象占用内存空间ObjectSize-master.zip" 提供的工具可能是一个帮助开发者分析和估算对象在内存中占用大小的解决方案。这样的工具对于调试、性能调优以及避免内存泄漏至关重要。 在Java中,对象的内存占用不...
- `MOV 07H, #07H`:这也是三字节指令,`MOV`占用一个字节,8位地址`07H`占用一个字节,8位立即数`#07H`占用一个字节。 此外,提到的BCD码减数求补问题是一个典型的应用示例。在51单片机中,由于`DA A`指令只能用于...
4. **创建字节集**:使用“创建字节集”命令创建一个新的字节集,并将编码后的数据填充进去。 5. **保存字节集**:将字节集写入文件,可以使用“写入文件全部字节”命令。 四、易语言中的相关函数 - **读取文件...
- "字节码+压缩存" 可能是一个实现将字节码形式的对象进行压缩并存储到 Redis 的类或方法。 - "字节码转字符存" 可能是将字节码解压后转化为字符形式(例如JSON或XML),以便于阅读和调试的工具。 总结,Redis 在...
在ASCII编码中,每个字符对应一个字节,但在Unicode编码(包括UTF-8)中,不同字符可能占用不同的字节数。UTF-8是最常用的Unicode变体,它根据字符的复杂性占用1至4个字节。 下面是一种基于UTF-8的JavaScript函数,...
查看当前数据库中每个表所占字节(空间)大小
在Java编程环境中,了解对象占用的内存大小是优化性能的关键步骤。这可以帮助我们避免内存泄漏,提高应用程序的效率。本文将深入探讨如何统计缓存(尤其是Java对象)所占的内存大小,以及这对理解程序内存消耗的重要...
在易语言中,字节集可以被看作一个数组,数组的每个元素都是一个字节。当需要将图片保存到文件或者在网络上传输时,通常会将图片转换成字节集形式。 在易语言图片句柄取图片字节集的操作中,主要是将内存中的图片...
在易语言中,字节集是一个重要的数据类型,用来存储二进制数据,例如图片、音频文件或者网络传输的数据。字节集的格式转换是编程过程中常见的一种操作,涉及到不同类型数据之间的互换。 字节集的格式转换通常包括两...
在E4A中,我们可以使用`ByteSet()`函数来创建一个空的字节集,或者使用`CopyToByteSet()`方法从其他数据结构复制数据。 3. **字节集操作**: - **添加/删除字节**:通过`AddByte()`和`RemoveByte()`方法,可以向...
一个空的String对象占用28个字节的内存,包含了一个指向字符数组的引用、一个偏移量、一个字符的长度和一个哈希码。当字符串内容非空时,内存占用会增加,因为需要存储字符数据。例如,字符串"ab"会占用28 + 2 * 2 =...