参考:
http://article.yeeyan.org/view/104091/62930?from_com
http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/index.html
http://www.ibm.com/developerworks/cn/java/j-codetoheap/index.html(好贴)
1 Java对象
一般而言,Java 对象在虚拟机的结构如下:
- 对象头(object header):8 个字节
- Java 原始类型数据:如 int, float, char 等类型的数据,各类型数据占内存如 表 1. Java 各数据类型所占内存.
- 引用(reference):4 个字节
- 填充符(padding)
数据类型 | 占用内存(字节数) |
---|---|
boolean | 1 |
byte | |
char | 2 |
short | |
int | 4 |
float | |
long | 8 |
double |
然而,一个 Java 对象实际还会占用些额外的空间,存储对象元数据,如:对象的 class 信息、ID、在虚拟机中的状态。在 Oracle JDK 的 Hotspot 虚拟机中,一个普通的对象需要额外 8 个字节。
这些元数据通常如下:
- 类:一个指向类信息的指针,描述了对象类型。举例来说,对于
java.lang.Integer
对象,这是java.lang.Integer
类的一个指针。 - 标记:一组标记,描述了对象的状态,包括对象的散列码(如果有),以及对象的形状(也就是说,对象是否是数组)。
- 锁:对象的同步信息,也就是说,对象目前是否正在同步。
加入在32位机器,创建java.lang.Integer对象,对象内存布局如下:
注意,其中前96位都是存储元数据,只有后面32位存储真实的int值。
2 Java数组对象
仍然以int型数组(注意不是Integer型数组)为例:
如上图,元数据多了一个size,描述数组的大小。size之后,用于存储诸多int
3 String类型
如果对于 String(JDK 6)的成员变量声明如下:
private final char value[]; private final int offset; private final int count; private int hash; |
这也就意味着,对于一个 8 个字符的字符串(128 位的 char
数据),需要有 256 位的数据用于字符数组,224 位的数据用于管理该数组的java.lang.String
对象,因此为了表示 128 位(16 个字节)的数据,总共需要占用 480 位(60 字节)。开销比例为 3.75:1。总体而言,数据结构越是复杂,开销就越高。
那么因该如何计算该 String 所占的空间?首先计算一个空的 char 数组所占空间,在 Java 里数组也是对象,因而数组也有对象头,故一个数组所占的空间为对象头所占的空间加上数组长度,即 8 + 4 = 12 字节 , 经过填充后为 16 字节。那么一个空 String 所占空间为:
对象头(8 字节)+ char 数组(16 字节)+ 3 个 int(3 × 4 = 12 字节)+1 个 char 数组的引用 (4 字节 ) = 40 字节。
因此一个实际的 String 所占空间的计算公式如下:
8*( ( 8+2*n+4+12)+7 ) / 8 = 8*(int) ( ( ( (n) *2 )+43) /8 );其中,n 为字符串长度。 |
4 从32位到64位
4.1 内存增长
上面讨论的都是在32位场景下,但是到了64位,这些对象元数据都会增长到64位,但存储的真实值数据却不会增长,客观导致浪费。如下图:
对于一个 64 位 Integer
对象,现在有 224 位的数据用于存储 int
字段所用的 32 位,开销比例是 7:1。对于一个 64 位单元素int
数组,有 288 位的数据用于存储 32 位 int
条目,开销比例是 9:1。这在实际应用程序中产生的影响在于,之前在 32 位 Java 运行时中运行的应用程序若迁移到 64 位 Java 运行时,其 Java 堆内存使用量会显著增加。通常情况下,增加的数量是原始堆大小的 70% 左右。举例来说,一个在 32 位 Java 运行时中使用 1GB Java 堆的 Java 应用程序在迁移到 64 位 Java 运行时之后,通常需要使用 1.7GB 的 Java 堆。这种内存增加并非仅限于 Java 堆。本机堆内存区使用量也会增加,有时甚至要增加 90% 之多。
4.2 各种类型对比
这个表是从参考3粘过来的,个人表示看不懂。。。。
字段类型 | 字段大小(位) | |||
---|---|---|---|---|
对象 | 数组 | |||
32 位 | 64 位 | 32 位 | 64 位 | |
boolean | 32 | 32 | 8 | 8 |
byte | 32 | 32 | 8 | 8 |
char | 32 | 32 | 16 | 16 |
short | 32 | 32 | 16 | 16 |
int | 32 | 32 | 32 | 32 |
float | 32 | 32 | 32 | 32 |
long | 32 | 32 | 64 | 64 |
double | 32 | 32 | 64 | 64 |
对象字段 | 32 | 64 (32*) | 32 | 64 (32*) |
对象元数据 | 32 | 64 (32*) | 32 | 64 (32*) |
* 对象字段的大小以及用于各对象元数据条目的数据的大小可通过 压缩引用或压缩 OOP 技术减小到 32 位。
4.3 压缩引用和压缩普通对象指针 (OOP)
IBM 和 Oracle JVM 分别通过压缩引用 (-Xcompressedrefs
) 和压缩 OOP (-XX:+UseCompressedOops
) 选项提供对象引用压缩功能。利用这些选项,即可在 32 位(而非 64 位)中存储对象字段和对象元数据值。在应用程序从 32 位 Java 运行时迁移到 64 位 Java 运行时的时候,这能消除 Java 堆内存使用量增加 70% 的负面影响。请注意,这些选项对于本机堆的内存使用无效,本机堆在 64 位 Java 运行时中的内存使用量仍然比 32 位 Java 运行时中的使用量高得多。
5 集合内存使用
最看不懂却最重要的一节!!!
.......
3 常见方法
http://jroller.com/page/mipsJava?entry=sizeof_java_objects -
使用System.gc(),Runtime.freeMemory(), Runtime.totalMemory()方法来计算java对象的大小。这个方法通常需要许多资源才能精确计算出对象的大小。它必须创建许多的需要估算对象的实例(最好是几千个),在创建的前后量测堆内存的大小。这个方法对于使用缓存机制的生产系统并不奏效。这个方法的优点是可以得到较为精确的结果,而不受Java实现版本和操作系统的影响。
http://www.javaspecialists.co.za/archive/Issue078.html -
他使用真实的原始类型大小的对照表来确定整个对象的大小。使用反射API遍历对象继承链上的成员变量并且计算所有原始类型变量的大小。这个方法不像上一方法那样需要很多的资源并能够用于缓存机制。弊端是原始类型大小的对照表会随着JVM实现版本的不同而不同,对于不同的实现版本需要重新计算。