关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JAVA »一个Java对象到底占用多大内存?

    一个Java对象到底占用多大内存?

    2015-02-08 00:00:00 出处:ImportNew - Calarence
    分享

    最近在读《深入理解Java虚拟机》,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存?

    在网上搜到了一篇博客讲的非常好:http://yueyemaitian.iteye.com/blog/2033046,里面提供的这个类也非常实用:

    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<Object>();  
            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);  
        }  
    }

    大家可以用这个代码边看边验证,注意的是,运行这个程序需要通过javaagent注入Instrumentation,具体可以看原博客。我今天主要是总结下手动计算Java对象占用字节数的基本规则,做为基本的技能必须get√,希望能帮到和我一样的Java菜鸟。

    在介绍之前,简单回顾下,Java对象的内存布局:对象头(Header),实例数据(Instance Data)和对齐填充(Padding),详细的可以看我的读书笔记。另外:不同的环境结果可能有差异,我所在的环境是HotSpot虚拟机,64位Windwos。

    下面进入正文:

    对象头

    对象头在32位系统上占用8bytes,64位系统上占用16bytes。

    实例数据

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

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

    reference类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes。

    对齐填充

    HotSpot的对齐方式为8字节对齐:

    (对象头 + 实例数据 + padding) % 8等于0且0 <= padding < 8

    指针压缩

    对象占用的内存大小收到VM参数UseCompressedOops的影响。

    1)对对象头的影响

    开启(-XX:+UseCompressedOops)对象头大小为12bytes(64位机器)。

    static class A {
            int a;
        }

    A对象占用内存情况:

    关闭指针压缩: 16+4=20不是8的倍数,所以+padding/4=24

    开启指针压缩: 12+4=16已经是8的倍数了,不需要再padding。

    1) 对reference类型的影响

    64位机器上reference类型占用8个字节,开启指针压缩后占用4个字节。

    static class B2 {
            int b2a;
            Integer b2b;
    }

    B2对象占用内存情况:

    关闭指针压缩: 16+4+8=28不是8的倍数,所以+padding/4=32

    开启指针压缩: 12+4+4=20不是8的倍数,所以+padding/4=24

    数组对象

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

    先考虑下new Integer[0]占用的内存大小,长度为0,即是对象头的大小:

    未开启压缩:24bytes

    开启压缩后:16bytes

    接着计算new Integer[1],new Integer[2],new Integer[3]和new Integer[4]就很容易了:

    未开启压缩:

    开启压缩:

    拿new Integer[3]来具体解释下:

    未开启压缩:24(对象头)+8*3=48,不需要padding;

    开启压缩:16(对象头)+3*4=28,+padding/4=32,其他依次类推。

    自定义类的数组也是一样的,比如:

    static class B3 {
            int a;
            Integer b;
        }

    new B3[3]占用的内存大小:

    未开启压缩:48

    开启压缩后:32

    复合对象

    计算复合对象占用内存的大小其实就是运用上面几条规则,只是麻烦点。

    1)对象本身的大小

    直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小; 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小。

    static class B {
            int a;
            int b;
        }
    static class C {
            int ba;
            B[] as = new B[3];
    
            C() {
                for (int i = 0; i < as.length; i++) {
                    as[i] = new B();
                }
            }
        }

    未开启压缩:16(对象头)+4(ba)+8(as引用的大小)+padding/4=32

    开启压缩:12+4+4+padding/4=24

    2)当前对象占用的空间总大小

    递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小。

    递归计算复合对象占用的内存的时候需要注意的是:对齐填充是以每个对象为单位进行的,看下面这个图就很容易明白。

    现在我们来手动计算下C对象占用的全部内存是多少,主要是三部分构成:C对象本身的大小+数组对象的大小+B对象的大小。

    未开启压缩:

    (16 + 4 + 8+4(padding)) + (24+ 8*3) +(16+8)*3 = 152bytes

    开启压缩:

    (12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(数组对象padding)) + (12+8+4(B对象padding))*3= 128bytes

    大家有兴趣的可以试试。

    实际工作中真正需要手动计算对象大小的场景应该很少,但是个人觉得做为基础知识每个Java开发人员都应该了解,另外:对自己写的代码大概占用多少内存,内存中是怎么布局的应该有一个直觉性的认识。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    IT头条

    Synology 对 Office 套件进行重大 AI 更新,增强私有云的生产力和安全性

    01:43

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    技术热点

    最全面的前端开发指南

    Windows7任务栏桌面下角的一些正在运行的图标不见了

    sql server快速删除记录方法

    SQL Server 7移动数据的6种方法

    SQL Server 2008的新压缩特性

    每个Java程序员必须知道的5个JVM命令行标志

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。