转载地址:http://www.importnew.com/1305.html

原文于2008年11月13日 发表, 2008年12月18日更新:这里还有一篇关于Java的Sizeof运算符的实用库的文章。

学C/C++出身的我,对Java有一点非常困惑,那就是缺乏计算对象占用内存大小的机制。而在C++中就可以通过sizeof运算符来获得基本类型以及类实例的大小。C和C++中的这个操作符对于指针运算、内存拷贝和IO操作都非常有用。

Java中并没有一个类似的运算符。事实上,Java也不需要这种运算符。Java中基本类型的大小在语言规范中已经定义了,而C/C++中基本类 型大小则跟平台相关。Java有自己的通过序列化构建的IO框架。再者,由于Java中没有指针,因此指针运算和内存块拷贝之类的操作也不存在。

但是,Java程序员有时还是希望能知道一个Java对象到底用了多少内存的。不过这个问题的答案并不简单。

首先要区分清楚的是shallow size和deep size。Shallow size是指对象自身占用的内存大小,其引用对象的大小不算在内。而deep size,则是自身所占内存大小和其递归引用的所有对象所占内存大小的总和。大多数情况下,你会希望获得一个对象的deep size,但是为了知道这个值,首先要知道怎么算shallow size,下面我来介绍一下。

有人抱怨JVM规范中没有针对运行时Java对象的内存结构的说明,这也就是说JVM供应商可以按照自己的需要来实现这一点。后果就是,同一个类在 不同的JVM上运行的实例对象占用的内存大小会有差别。好在是世界上大部分人(包括我在内)都使用Sun HotSpot虚拟机,这就大大简化了这个问题。我们接下来的讨论也会基于32位的Sun公司的JVM。下面我介绍一些规则来辅助解释JVM如何组织对象 在内存中的布局的。

没有实例属性的类的内存布局

在Sun JVM中,(除了数组之外的)对象都有两个机器字(words)的头部。第一个字中包含这个对象的标示哈希码以及其他一些类似锁状态和等标识信息,第二个 字中包含一个指向对象的类的引用。另外,任何对象都是8个字节为粒度进行对齐的。这就是对象内存布局的第一个规则:

规则1:任何对象都是8个字节为粒度进行对齐的。

比如,如果调用new Object(),由于Object类并没有其他没有其他可存储的成员,那么仅仅使用堆中的8个字节来保存两个字的头部即可。

继承了Object的类的内存布局

除了上面所说的8个字节的头部,类属性紧随其后。属性通常根据其大小来排列。例如,整型(int)以4个字节为单位对齐,长整型(long)以8个 字节为单位对齐。这里是出于性能考虑而这么设计的:通常情况下,如果数据以4字节为单位对齐,那么从内存中读4字节的数据并写入到处理器的4字节寄存器是 性价比更高的。

为了节省内存,Sun VM并没有按照属性声明时的顺序来进行内存布局。实际上,属性在内存中按照下面的顺序来组织:

1. 双精度型(doubles)和长整型(longs)

2. 整型(ints)和浮点型(floats)

3. 短整型(shorts)和字符型(chars)

4. 布尔型(booleans)和字节型(bytes)

5. 引用类型(references)

内存使用率会通过这个机制得到优化。例如,如下声明一个类:

class MyClass {

       byte a;

       int c;

       boolean d;

       long e;

       Object f;         

}

如果JVM并没有打乱属性的声明顺序,其对象内存布局将会是下面这个样子:

[HEADER:  8 bytes]  8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 4 bytes] 16
[d: 1 byte ] 17
[padding: 7 bytes] 24
[e: 8 bytes] 32
[f: 4 bytes] 36
[padding: 4 bytes] 40

此时,用于占位的14个字节是浪费的,这个对象一共使用了40个字节的内存空间。但是,如果用上面的规则对这些对象重新排序,其内存结果会变成下面这个样子:

[HEADER:  8 bytes]  8
[e: 8 bytes] 16
[c: 4 bytes] 20
[a: 1 byte ] 21
[d: 1 byte ] 22
[padding: 2 bytes] 24
[f: 4 bytes] 28
[padding: 4 bytes] 32

这次,用于占位的只有6个字节,这个对象使用了32个字节的内存空间。

因此,对象内存布局的第二个规则是:

规则2:类属性按照如下优先级进行排列:长整型和双精度类型;整型和浮点型;字符和短整型;字节类型和布尔类型,最后是引用类型。这些属性都按照各自的单位对齐。

现在我们知道如何计算一个继承了Object的类的实例的内存大小了。下面这个例子用来做下练习: java.lang.Boolean。这是其内存布局:

[HEADER:  8 bytes]  8
[value: 1 byte ] 9
[padding: 7 bytes] 16

Boolean类的实例占用16个字节的内存!惊讶吧?(别忘了最后用来占位的7个字节)。

继承其他类的子类的内存布局

JVM所遵守的下面3个规则用来组织有父类的类的成员。对象内存布局的规则3如下:

规则3:不同类继承关系中的成员不能混合排列。首先按照规则2处理父类中的成员,接着才是子类的成员。

举例如下:

class A {
long a;
int b;
int c;
} class B extends A {
long d;
}

类B的实例在内存中的存储如下:

[HEADER:  8 bytes]  8
[a: 8 bytes] 16
[b: 4 bytes] 20
[c: 4 bytes] 24
[d: 8 bytes] 32

如果父类中的成员的大小无法满足4个字节这个基本单位,那么下一条规则就会起作用:

规则4:当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话,就必须扩展到4个字节的基本单位。

举例如下:

class A {
byte a;
} class B {
byte b;
}
[HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[b: 1 byte ] 13
[padding: 3 bytes] 16

注意到成员a被扩充了3个字节以保证和成员b之间的间隔是4个字节。这个空间不能被类B使用,因此被浪费了。

最后一条规则在下面情况下用来节省一些空间:如果子类成员是长整型或双精度类型,并且父类并没有用完8个字节。

规则5:如果子类第一个成员是一个双精度或者长整型,并且父类并没有用完8个字节,JVM会破坏规则2,按照整形(int),短整型(short),字节型(byte),引用类型(reference)的顺序,向未填满的空间填充。

举例如下:

class A {
byte a;
} class B {
long b;
short c;
byte d;
}

其内存布局如下:

[HEADER:  8 bytes]  8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 2 bytes] 14
[d: 1 byte ] 15
[padding: 1 byte ] 16
[b: 8 bytes] 24

在第12字节处,类A“结束”的地方,JVM没有遵守规则2,而是在长整型之前插入一个短整型和一个字节型成员,这样可以避免浪费3个字节。

数组的内存布局

数组有一个额外的头部成员,用来存放“长度”变量。数组元素以及数组本身,跟其他常规对象同样,都需要遵守8个字节的边界规则。

下面是一个有3个元素的字节数组的内存布局:

[HEADER:  12 bytes] 12
[[0]: 1 byte ] 13
[[1]: 1 byte ] 14
[[2]: 1 byte ] 15
[padding: 1 byte ] 16

下面是一个有3个元素的长整型数字的内存布局:

[HEADER:  12 bytes] 12
[padding: 4 bytes] 16
[[0]: 8 bytes] 24
[[1]: 8 bytes] 32
[[2]: 8 bytes] 40

内部类的内存布局

非静态内部类(Non-static inner classes)有一个额外的“隐藏”成员,这个成员是一个指向外部类的引用变量。这个成员是一个普通引用,因此遵守引用内存布局的规则。内部类因此有4个字节的额外开销。

最后的一点想法

我们已经学习了在32位Sun JVM中如何计算Java对象的shallow size。知道内存是如何组织的有助于理解类实例占用的内存数。

下一篇文章中,会有些示例代码,这些代码会把相关内容整理到一起,用反射(reflection)来计算一个对象的deep size。如果你感兴趣,请订阅此源或者等待这个博客的更新吧!

英文原文:Code Instructions,翻译:ImportNew - 郑雯

译文链接: http://www.importnew.com/1305.html

[Java基础] Java对象内存结构的更多相关文章

  1. 16.Java基础_对象内存图

    单个对象 1.进入main函数,生成main函数的栈空间 2.先在mian函数的栈空间里产生一个Student变量(以后会用来存储Studnet对象的首地址),然后进入Student类,在堆内存创建S ...

  2. JAVA 对象内存结构

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

  3. Synchronized加锁、锁升级和java对象内存结构

    首先了解一下JMM中定义的内存操作: 一个线程操作数据时候都是从主内存(堆内存)读取到自己工作内存(线程私有的数据区域)中再进行操作.对于硬件内存来说,并没有工作内存和主内存的区分,这都是java内存 ...

  4. Java基础-Java中的堆内存和离堆内存机制

    Java基础-Java中的堆内存和离堆内存机制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.

  5. Java基础-Java中的内存分配与回收机制

    Java基础-Java中的内存分配与回收机制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一. 二.

  6. Java基础-Java中23种设计模式之常用的设计模式

    Java基础-Java中23种设计模式之常用的设计模式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   一.设计模式分类 设计模式是针对特定场景给出的专家级的解决方案.总的来说设 ...

  7. Java基础-JAVA中常见的数据结构介绍

    Java基础-JAVA中常见的数据结构介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是数据结构 答:数据结构是指数据存储的组织方式.大致上分为线性表.栈(Stack) ...

  8. java基础---->java中正则表达式二

    跟正则表达式相关的类有:Pattern.Matcher和String.今天我们就开始Java中正则表达式的学习. Pattern和Matcher的理解 一.正则表达式的使用方法 一般推荐使用的方式如下 ...

  9. Java基础-Java数据类型

    Java基础-Java数据类型 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数据类型的作用 数据类型就是一组值,以及这一组值上的操作,数据类型可以决定数据的存储方式,取值范围 ...

  10. Java基础-Java中的并法库之重入读写锁(ReentrantReadWriteLock)

    Java基础-Java中的并法库之重入读写锁(ReentrantReadWriteLock) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在学习Java的之前,你可能已经听说过读 ...

随机推荐

  1. SQL语句获取时间的方法

    1. 当前系统日期.时间select getdate() 2. dateadd 在向指定日期加上一段时间的基础上,返回新的 datetime 值例如:向日期加上2天select dateadd(day ...

  2. C#多线程编程之:集合类中Synchronized方法与SyncRoot属性原理分析

    我们知道,在.net的一些集合类型中,譬如Hashtable和ArrayList,都有Synchronized静态方法和SyncRoot属性,他们之间有联系吗?我怎么才能用好他们呢? 以Hashtab ...

  3. SRM 563 Div1 500 SpellCards

    Description 有n张符卡排成一个队列,每张符卡有两个属性,等级lili和伤害didi. 你可以做任意次操作,每次操作为以下二者之一: 把队首的符卡移动到队尾. 使用队首的符卡,对敌人造成di ...

  4. UVA - 796

    UVA- 796 /** 题意:给出一个图,然后看此图的存在的桥,并且输出是哪一个, 做法:Tarjan(不存在重边) **/ #include<iostream> #include< ...

  5. Edit Distance——经典的动态规划问题

    题目描述Edit DistanceGiven two words word1 and word2, find the minimum number of steps required to conve ...

  6. Introducing the Filter Types

    The ActionFilterAttribute class implements both the IActionFilter and IResultFilter interfaces. This ...

  7. NET/ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)

    阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...

  8. POJ1958 Strange Towers of Hanoi [递推]

    题目传送门 Strange Towers of Hanoi Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 3117   Ac ...

  9. Xamarin.Forms教程下载安装Xamarin.iOS

    Xamarin.Forms教程下载安装Xamarin.iOS 下载安装Xamarin.iOS Xamarin.iOS可以为Mac上iOS应用程序在Windows计算机上编写和测试网络提供构建和部署服务 ...

  10. angularjs学习笔记3-directive中scope的绑定修饰符

    在angularjs中,一个directive返回一个对象,对象存在很多属性,并且可以在directive中自定义自己的scope,而使用自己的scope是为了防止一个directive被使用在多个地 ...