佛山做网站公司排名,wordpress添加新页面,上海企业网站建设推荐,怎样建设网站是什么样的转载自 Java对象内存结构学C/C出身的我#xff0c;对Java有一点非常困惑#xff0c;那就是缺乏计算对象占用内存大小的机制。而在C中就可以通过sizeof运算符来获得基本类型以及类实例的大小。C和C中的这个操作符对于指针运算、内存拷贝和IO操作都非常有用。
Java中并没有一个…转载自 Java对象内存结构学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
内存使用率会通过这个机制得到优化。例如如下声明一个类
12345678910111213classMyClass { bytea; intc; booleand; longe; Object f; }如果JVM并没有打乱属性的声明顺序其对象内存布局将会是下面这个样子123456789[HEADER: 8bytes] 8[a: 1byte ] 9[padding:3bytes] 12[c: 4bytes] 16[d: 1byte ] 17[padding:7bytes] 24[e: 8bytes] 32[f: 4bytes] 36[padding:4bytes] 40此时用于占位的14个字节是浪费的这个对象一共使用了40个字节的内存空间。但是如果用上面的规则对这些对象重新排序其内存结果会变成下面这个样子
12345678[HEADER: 8bytes] 8[e: 8bytes] 16[c: 4bytes] 20[a: 1byte ] 21[d: 1byte ] 22[padding:2bytes] 24[f: 4bytes] 28[padding:4bytes] 32这次用于占位的只有6个字节这个对象使用了32个字节的内存空间。
因此对象内存布局的第二个规则是
规则2类属性按照如下优先级进行排列长整型和双精度类型整型和浮点型字符和短整型字节类型和布尔类型最后是引用类型。这些属性都按照各自的单位对齐。
现在我们知道如何计算一个继承了Object的类的实例的内存大小了。下面这个例子用来做下练习: java.lang.Boolean。这是其内存布局
123[HEADER: 8bytes] 8[value: 1byte ] 9[padding:7bytes] 16Boolean类的实例占用16个字节的内存惊讶吧别忘了最后用来占位的7个字节。
继承其他类的子类的内存布局
JVM所遵守的下面3个规则用来组织有父类的类的成员。对象内存布局的规则3如下
规则3不同类继承关系中的成员不能混合排列。首先按照规则2处理父类中的成员接着才是子类的成员。
举例如下
123456789classA { longa; intb; intc;}classB extendsA { longd;}类B的实例在内存中的存储如下
12345[HEADER: 8bytes] 8[a: 8bytes] 16[b: 4bytes] 20[c: 4bytes] 24[d: 8bytes] 32如果父类中的成员的大小无法满足4个字节这个基本单位那么下一条规则就会起作用
规则4当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话就必须扩展到4个字节的基本单位。
举例如下
123456789101112classA { bytea;}classB { byteb;}[HEADER: 8bytes] 8[a: 1byte ] 9[padding:3bytes] 12[b: 1byte ] 13[padding:3bytes] 16注意到成员a被扩充了3个字节以保证和成员b之间的间隔是4个字节。这个空间不能被类B使用因此被浪费了。
最后一条规则在下面情况下用来节省一些空间如果子类成员是长整型或双精度类型并且父类并没有用完8个字节。
规则5如果子类第一个成员是一个双精度或者长整型并且父类并没有用完8个字节JVM会破坏规则2按照整形int短整型short字节型byte引用类型reference的顺序向未填满的空间填充。
举例如下
123456789classA { bytea;}classB { longb; shortc; byted;}其内存布局如下
1234567[HEADER: 8bytes] 8[a: 1byte ] 9[padding:3bytes] 12[c: 2bytes] 14[d: 1byte ] 15[padding:1byte ] 16[b: 8bytes] 24在第12字节处类A“结束”的地方JVM没有遵守规则2而是在长整型之前插入一个短整型和一个字节型成员这样可以避免浪费3个字节。
数组的内存布局
数组有一个额外的头部成员用来存放“长度”变量。数组元素以及数组本身跟其他常规对象同样都需要遵守8个字节的边界规则。
下面是一个有3个元素的字节数组的内存布局
12345[HEADER: 12bytes] 12[[0]: 1byte ] 13[[1]: 1byte ] 14[[2]: 1byte ] 15[padding: 1byte ] 16下面是一个有3个元素的长整型数字的内存布局
12345[HEADER: 12bytes] 12[padding: 4bytes] 16[[0]: 8bytes] 24[[1]: 8bytes] 32[[2]: 8bytes] 40内部类的内存布局
非静态内部类Non-static inner classes有一个额外的“隐藏”成员这个成员是一个指向外部类的引用变量。这个成员是一个普通引用因此遵守引用内存布局的规则。内部类因此有4个字节的额外开销。
最后的一点想法
我们已经学习了在32位Sun JVM中如何计算Java对象的shallow size。知道内存是如何组织的有助于理解类实例占用的内存数。英文原文Code Instructions翻译ImportNew - 郑雯
译文链接 http://www.importnew.com/1305.html