一个空对象占用多大的内存

引用

最近在读《深入理解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。

image
image

对象头包含什么东西

Mark Word(标记字)和Class Pointer(类指针),如果是数组对象还得再加一项Array Length(数组长度)。


image.png
image.png

视频链接:https://www.zhihu.com/zvideo/1319373829861433344

实例数据

原生类型(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.1对对象头的影响

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

static class A {
int a;
}

A对象占用内存情况:
关闭指针压缩: 16+4=20不是8的倍数,所以+padding/4=24


image.png

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

image.png

1.2 对reference类型的影响

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

static class B2 {
int b2a;
Integer b2b;
}

B2对象占用内存情况:

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


image.png

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

image.png

数组对象

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

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

未开启压缩:24bytes


image.png

开启压缩后:16bytes

image.png

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

image

开启压缩:

image

拿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)当前对象占用的空间总大小

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

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


image.png

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

未开启压缩:

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

开启压缩:

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

大家有兴趣的可以试试。

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

转自:https://www.cnblogs.com/zhanjindong/p/3757767.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容