JVM —— Java 对象占用空间大小计算

来源:互联网 发布:什么决定命运知乎 编辑:程序博客网 时间:2024/05/19 02:28
零. 为什么要知道 Java 对象占用空间大小
  1. 缓存的实现: 在设计 JVM 内缓存时(不是借助 Memcached、 Redis 等), 需要知道缓存的对象是否会超过 JVM 最大堆限制, 如果会超过要设置相应算法如 LRU 来丢弃一部分缓存数据以满足后续内容的缓存
  2. JVM 参数设置: 如果知道对象会被创建, 可以帮助判断 -Xmx 需要设置多少
  3. 只是为了好玩


一. 对象的内存布局
HotSpot 虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。


二. 对象头
JVM 对象头一般占用两个机器码,在 32-bit JVM 上占用 64bit, 在 64-bit JVM 上占用 128bit 即 8+8=16 bytes(开启指针压缩后占用 4+8=12 bytes

64位机器上,数组对象的对象头占用 24 bytes,启用压缩之后占用 16 bytes。之所以比普通对象占用内存多是因为需要额外的空间存储数组的长度。

更具体的对象头介绍请参考:http://blog.csdn.net/wenniuwuren/article/details/50939410


三. 实例数据

原生类型(primitive type)的内存占用如下:

Primitive TypeMemory Required(bytes)boolean                      1byte                            1short                           2char                            2int                               4float                            4long                            8double    8

引用类型(reference type: Integer)在 32 位系统上每个占用 4bytes(即32bit, 才能管理 2^32=4G 的内存), 在 64 位系统上每个占用 8bytes(开启压缩为 4 bytes)。


四. 对齐填充
HotSpot 的对齐方式为 8 字节对齐,不足的需要 Padding 填充对齐, 公式:(对象头 + 实例数据 + padding)% 8 == 0 (0<= padding <8)


五. 计算 Java 对象占用空间大小
借助 Instrument 接口的 getObjectSize 方法计算对象占用空间

SizeOfAgent: 计算对象大小类 

package com.wenniuwuren.objectsizeof;import java.lang.instrument.Instrumentation;import java.lang.reflect.Array;import java.lang.reflect.Field;import java.lang.reflect.Modifier;import java.util.IdentityHashMap;import java.util.Map;import java.util.Stack;/** * 借助 Instrumentation 接口的 getObjectSize 方法计算对象占用空间 * 原来的 sizeOf 只能计算本对象占用空间, 无法计算继承下来的占用空间, * 不过可以用反射的方法把全部占用空间计算出来 * * Created by zhuyb on 16/3/20. */public class SizeOfAgent {    static Instrumentation instrumentation;    // 第一个参数由 –javaagent, 第二个参数由 JVM 传入    public static void premain(String agentArgs, Instrumentation instP) {        instrumentation = instP;    }    // 返回没有子类对象大小的大小    public static long sizeOf(Object o) {        if (instrumentation == null) {            throw new IllegalStateException("Can not access instrumentation environment.\n" +                    "Please check if jar file containing SizeOfAgent class is \n" +                    "specified in the java's \"-javaagent\" command line argument.");        }        return instrumentation.getObjectSize(o);    }    /**     *     * 计算复合对象     * @param obj object to calculate size of     * @return object size     */    public static long fullSizeOf(Object obj) {        Map<Object, Object> visited = new IdentityHashMap<Object, Object>();        Stack<Object> stack = new Stack<Object>();        long result = internalSizeOf(obj, stack, visited);        while (!stack.isEmpty()) {            result += internalSizeOf(stack.pop(), stack, visited);        }        visited.clear();        return result;    }    // 这个算法使每个对象仅被计算一次, 避免循环引用,即死循环计算    private static boolean skipObject(Object obj, Map<Object, Object> visited) {        if (obj instanceof String) {            // String 池里已有的不再计算            if (obj == ((String) obj).intern()) {                return true;            }        }        return (obj == null) // 已有对象不再计算                || visited.containsKey(obj);    }    private static long internalSizeOf(Object obj, Stack<Object> stack,  Map<Object, Object> visited) {        if (skipObject(obj, visited)){            return 0;        }        visited.put(obj, null);        long result = 0;        // get size of object + primitive variables + member pointers        result += SizeOfAgent.sizeOf(obj);        // 处理所有数组内容        Class clazz = obj.getClass();        if (clazz.isArray()) {            // [I , [F 基本类型名字长度是2            if(clazz.getName().length() != 2) {// skip primitive type array                int length =  Array.getLength(obj);                for (int i = 0; i < length; i++) {                    stack.add(Array.get(obj, i));                }            }            return result;        }        // 处理对象的所有字段        while (clazz != null) {            Field[] fields = clazz.getDeclaredFields();            for (int i = 0; i < fields.length; i++) {                // 不重复计算静态类型字段                if (!Modifier.isStatic(fields[i].getModifiers())) {                    // 不重复计算原始类型字段                    if (fields[i].getType().isPrimitive()) {                        continue;                    } else {                        // 使 private 属性可访问                        fields[i].setAccessible(true);                        try {                            // objects to be estimated are put to stack                            Object objectToAdd = fields[i].get(obj);                            if (objectToAdd != null) {                                stack.add(objectToAdd);                            }                        } catch (IllegalAccessException ex) {                            assert false;                        }                    }                }            }            clazz = clazz.getSuperclass();        }        return result;    }}

使用上述代码必须将上述代码打成 jar 包, 并且 MANIFEST.MF 文件设置参数(
    Premain-Class: sizeof.agent.SizeOfAgent
    Boot-Class-Path:
    Can-Redefine-Classes:
 false

如果使用 Maven 打包的话, 可以直接在 pom.xml 里面设置 MANIFEST.MF 的参数 :
<!-- pom打jar包的时候设置MANIFEST.MF的key/value,可以通过在pom.xml文件中添加plugin的方式来实现 -->    <build>        <plugins>            <plugin>                <artifactId>maven-jar-plugin</artifactId>                <version>2.4</version>                <configuration>                    <finalName>SizeOfAgent</finalName>                    <archive>                        <manifestEntries>                            <Premain-class>com.wenniuwuren.objectsizeof.SizeOfAgent</Premain-class>                            <Boot-Class-Path></Boot-Class-Path>                            <Can-Redefine-Classes>false</Can-Redefine-Classes>                        </manifestEntries>                        <addMavenDescriptor>false</addMavenDescriptor>                    </archive>                </configuration>            </plugin>        </plugins>    </build>

测试类: SizeOfAgentTest 
package com.wenniuwuren.objectsizeof;import static com.wenniuwuren.objectsizeof.SizeOfAgent.*;/** * 以下结果在 64-bit JVM 下测试 * 启动参数1(不压缩指针长度):-javaagent:target/SizeOfAgent.jar -XX:-UseCompressedOops * * Created by zhuyb on 16/3/20. */public class SizeOfAgentTest {    public static void main(String[] args) {        System.out.println("------------------空对象----------------------------");        // 16 bytes + 0 + 0 = 16  空对象, 只有对象头        System.out.println("sizeOf(new Object()) = " + sizeOf(new Object()));        System.out.println("fullSizeOf(new Object()) = " + fullSizeOf(new Object()));        System.out.println("----------------非空对象含有原始类型、引用类型------------------------------");        // 16 bytes + 8 + 4 + padding = 32        System.out.println("sizeOf(new A()) = " + sizeOf(new A()));        System.out.println("fullSizeOf(new A()) = " + fullSizeOf(new A()));        // 16 + 4 + padding =24      数据是一个 int        System.out.println("sizeOf(new Integer(1)) = " + sizeOf(new Integer(1)));        // (16 + int hash:4 + int hash32:4 + refer char value[]:8 + padding) = 32        // 静态属性(static)不计算空间,因为所有对象都是共享一块空间的        // 不同版本JDK可能 String 内部 Field 可能不同,本次测试使用JDK1.7        System.out.println("sizeOf(new String()) = " + sizeOf(new String()));        // (16 + 4 + 4 + 8 + padding) + (24 + 0 + padding) = 56        System.out.println("fullSizeOf(new String()) = " + fullSizeOf(new String()));        // (16 + 4 + 4 + 8 + padding) = 32        System.out.println("sizeOf(new String('a')) = " + sizeOf(new String("a")));        // (16 + 4 + 4 + 8 +padding)  +  (24 + 2 + padding) = 64        System.out.println("fullSizeOf(new String('a')) = " + fullSizeOf(new String("a")));        System.out.println("-------------------原始类型数组对象---------------------------");        // 24 bytes + 0*1 + 0 = 24      数组长度为 0,所以只有对象头的长度        System.out.println("sizeOf(new byte[0]) = " + sizeOf(new byte[0]));        System.out.println("fullSizeOf(new byte[0]) = " + fullSizeOf(new byte[0]));        // 24 + 1*1 + padding = 32        System.out.println("sizeOf(new byte[1]) = " + sizeOf(new byte[1]));        System.out.println("fullSizeOf(new byte[1]) = " + fullSizeOf(new byte[1]));        // 24 + 1*2 + padding = 32        System.out.println("sizeOf(new char[1]) = " + sizeOf(new char[1]));        System.out.println("fullSizeOf(new char[1]) = " + fullSizeOf(new char[1]));        // 24 + 9*1 + padding = 40        System.out.println("sizeOf(new byte[9]) = " + sizeOf(new byte[9]));        System.out.println("fullSizeOf(new byte[9]) = " + fullSizeOf(new byte[9]));        System.out.println("--------------------引用类型数组对象--------------------------");        // 24 bytes + 0*8 + 0  = 24       数组长度为 0        System.out.println("sizeOf(new Integer[0]) = " + sizeOf(new Integer[0]));        System.out.println("fullSizeOf(new Integer[0]) = " + fullSizeOf(new Integer[0]));        // 24 bytes + 1*8 + 0 = 32    引用对象 64-bit JVM 占用 8 bytes        System.out.println("sizeOf(new Integer[1]) = " + sizeOf(new Integer[1]));        System.out.println("fullSizeOf(new Integer[1]) = " + fullSizeOf(new Integer[1]));        // 24 bytes + 2*8 + padding = 40        System.out.println("sizeOf(new Integer[1]) = " + sizeOf(new Integer[1]));        System.out.println("fullSizeOf(new Integer[1]) = " + fullSizeOf(new Integer[1]));        // 24 + 3*8 + padding = 48        System.out.println("sizeOf(new Integer[3]) = " + sizeOf(new Integer[3]));        System.out.println("fullSizeOf(new Integer[3]) = " + fullSizeOf(new Integer[3]));        System.out.println("-------------------自定义数组对象---------------------------");        // 16 + (4+8) + padding = 32        System.out.println("sizeOf(new B()) = " + sizeOf(new B()));        System.out.println("fullSizeOf(new B()) = " + fullSizeOf(new B()));        // 24 + 0*8 + padding = 24    引用对象 64-bit JVM 占用 8 bytes,        // 因为没创建真实的 new B()所以 B类内部数据还未占用空间        System.out.println("sizeOf(new B[0]) = " + sizeOf(new B[0]));        System.out.println("fullSizeOf(new B[0]) = " + fullSizeOf(new B[0]));        // 24 + 1*8 + padding = 32        System.out.println("sizeOf(new B[1]) = " + sizeOf(new B[1]));        System.out.println("fullSizeOf(new B[1]) = " + fullSizeOf(new B[1]));        // 24 + 2*8 + padding = 40        System.out.println("sizeOf(new B[2]) = " + sizeOf(new B[2]));        System.out.println("fullSizeOf(new B[2]) = " + fullSizeOf(new B[2]));        // 24 + 3*8 + padding = 48        System.out.println("sizeOf(new B[3]) = " + sizeOf(new B[3]));        System.out.println("fullSizeOf(new B[3]) = " + fullSizeOf(new B[3]));        System.out.println("-------------------复合对象---------------------------");        // 16 + (4+8) + padding = 32  sizeOf 只计算单层次占用空间大小        System.out.println("sizeOf(new C()) = " + sizeOf(new C()));        // (16 + (4+8) + padding1) + (24 + 2*8 + padding2) + 2*(16 + (4+8) + padding3) = 136        // 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小        System.out.println("fullSizeOf(new C()) = " + fullSizeOf(new C()));        System.out.println("-------------------继承关系---------------------------");        // 涉及继承关系的时候有一个最基本的规则:首先存放父类中的成员,接着才是子类中的成员, 父类也要按照 8 byte 规定        // 16 + 1 + padding = 24        System.out.println("sizeOf(new D()) = " + sizeOf(new D()));        System.out.println("fullSizeOf(new D()) = " + fullSizeOf(new D()));        // 16 + 父类(1 + padding1) + 1 + padding2 = 32        System.out.println("sizeOf(new E()) = " + sizeOf(new E()));        System.out.println("fullSizeOf(new E()) = " + fullSizeOf(new E()));    }    public static class A {        int a;        Integer b;    }    public static class B {        int a;        Integer b;    }    public static class C{        int c;        B[] b = new B[2];        // 初始化        C() {            for (int i = 0; i < b.length; i++) {                b[i] = new B();            }        }    }    public static class D {        byte d1;    }    public static class E extends D {        byte e1;    }}


运行:
如果在 IDE 运行时需要设置 JVM 参数: -javaagent:target/SizeOfAgent.jar -XX:-UseCompressedOops;
如果在命令行运行命令: java -javaagent:sizeofag.jar-XX:-UseCompressedOops 主类名称。 

测试结果:
------------------空对象----------------------------sizeOf(new Object()) = 16fullSizeOf(new Object()) = 16----------------非空对象含有原始类型、引用类型------------------------------sizeOf(new A()) = 32fullSizeOf(new A()) = 32sizeOf(new Integer(1)) = 24sizeOf(new String()) = 32fullSizeOf(new String()) = 56sizeOf(new String('a')) = 32fullSizeOf(new String('a')) = 64-------------------原始类型数组对象---------------------------sizeOf(new byte[0]) = 24fullSizeOf(new byte[0]) = 24sizeOf(new byte[1]) = 32fullSizeOf(new byte[1]) = 32sizeOf(new char[1]) = 32fullSizeOf(new char[1]) = 32sizeOf(new byte[9]) = 40fullSizeOf(new byte[9]) = 40--------------------引用类型数组对象--------------------------sizeOf(new Integer[0]) = 24fullSizeOf(new Integer[0]) = 24sizeOf(new Integer[1]) = 32fullSizeOf(new Integer[1]) = 32sizeOf(new Integer[1]) = 32fullSizeOf(new Integer[1]) = 32sizeOf(new Integer[3]) = 48fullSizeOf(new Integer[3]) = 48-------------------自定义数组对象---------------------------sizeOf(new B()) = 32fullSizeOf(new B()) = 32sizeOf(new B[0]) = 24fullSizeOf(new B[0]) = 24sizeOf(new B[1]) = 32fullSizeOf(new B[1]) = 32sizeOf(new B[2]) = 40fullSizeOf(new B[2]) = 40sizeOf(new B[3]) = 48fullSizeOf(new B[3]) = 48-------------------复合对象---------------------------sizeOf(new C()) = 48fullSizeOf(new C()) = 152-------------------继承关系---------------------------sizeOf(new D()) = 24fullSizeOf(new D()) = 24sizeOf(new E()) = 32fullSizeOf(new E()) = 32

测试类中复合对象计算可能较为麻烦, 可以参照下图较为清楚地看出 new C() 的占用空间计算:



六. 总结
整体的 Java 对象是按照一定规则进行的, 清楚了 JVM 对象的内存布局和分配规则, 计算 Java 对象的大小就比较简单了。
Java 不像 C++ 可以提供对象大小, 这是 Java 语言的设计初衷(自动内存管理), 但是随着对 Java 的深入了解, 又到了对 JVM (使用 C、C++ 实现) 底层实现的问题上。

本文的参考资料为 2007 年的, 至今已有 9 年, 参考资料内容至今还是有效的,JVM 相关的东西变动确实小,挺有意思的


七. 参考资料
http://www.jroller.com/maxim/entry/again_about_determining_size_of


4 0