3

解析一个Java对象占用多少内存空间

 2 years ago
source link: https://blog.csdn.net/renfufei/article/details/95758333
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

解析一个Java对象占用多少内存空间

铁锚 2019-07-13 16:02:15 9762
专栏收录该内容
51 篇文章 1 订阅

说明: alignment, 对齐, 比如8字节的数据类型long, 在内存中的起始地址必须是8的整数倍。

padding, 补齐; 在对象所占据空间的末尾,如果有空白, 需要使用padding来补齐, 因为下一个对象的起始位置必须是4/8字节(32bit/64bit)的整数倍(这又是一种对齐)。

一个对象具有100个属性, 与100个对象每个具有1个属性, 哪个占用的内存空间更大?

一个对象会分配多少内存?

每增加一个属性,对象占用的空间会增加多少?

参考 Mindprod , 可以发现事情并不简单:

JVM具体实现可以用任意形式来存储内部数据, 可以是大端字节序或者小端字节序(big or little endian), 还可以增加任意数量的补齐、或者开销, 尽管原生数据类型(primitives)的行为必须符合规范。
例如, JVM或者本地编译器可以决定是否将 boolean[] 存储为64bit的内存块中, 类似于 BitSet。 厂商可以不告诉你这些细节, 只要程序运行结果一致即可。

  • JVM可以在栈(stack)空间分配一些临时对象。
  • 编译器可能用常量来替换某些变量或方法调用。
  • JVM可能对方法和循环生成多个编译版本; 例如, 编译两种版本的方法, 针对某些情况调用其中的一个。

当然, 硬件平台和操作系统还会有多级缓存, 例如CPU内置的L1/L2/L3; SRAM缓存, DRAM缓存, 普通内存, 以及磁盘上的虚拟内存。 用户数据可能在多个层级的缓存中出现. 这么多复杂的情况、决定了我们只能对内存占用情况进行大致的估测。

可以使用 Instrumentation.getObjectSize() 方法来估算一个对象占用的内存空间。

想要查看对象的实际布局(layout)、占用(footprint)、以及引用(reference), 可以使用OpenJDK提供的 JOL工具(Java Object Layout)

对象头和对象引用

在64位JVM中, 对象头占据的空间是 12-byte(=96bit=64+32), 但是以8字节对齐, 所以一个空类的实例至少占用16字节。

在32位JVM中, 对象头占8个字节, 以4的倍数对齐(32=4*8)。(请参考 Dmitry Spikhalskiy,Jayen的回答,以及JavaWorld网站)。

通常, 在32位JVM, 以及内存小于 -Xmx32G 的64位JVM上, 一个引用占的内存是4个字节。(指针压缩)

因此, 64位JVM一般需要多消耗 30%-50% 堆内存。(参考: Should I use a 32- or a 64-bit JVM?, 2012, JDK 1.7)

包装类型、数组和字符串

包装类型比原生数据类型消耗的内存要多, 参考 JavaWorld :

  • Integer: 占用16字节(8+4=12+补齐), 因为 int 部分占4个字节。 所以使用 Integer 比原生类型 int 要多消耗 300% 的内存。
  • Long: 一般占用16个字节(8+8=16): 当然, 对象的实际大小由底层平台的内存对齐确定, 具体由特定CPU平台的JVM实现决定。 看起来一个Long 类型的对象, 比起原生类型long多占用了8个字节。 相比之下, Integer有4字节的补齐, 很可能是因为JVM强制进行了8字节的边界对齐。

其他容器占用的空间也不小:

  • 多维数组: 这是另一个惊喜。
    在进行数值或科学计算时, 开发人员经常会使用 int[dim1][dim2] 这种构造方式。
    在二维数组 int[dim1][dim2] 中, 每个嵌套的数组 int[dim2] 都是一个单独的 Object, 会额外占用16字节的空间。某些情况下,这种开销是一种浪费。当数组维度更大时,这种开销特别明显。
    例如, int[128][2] 实例占用3600字节。 而 int[256] 实例则只占用1040字节。里面的有效存储空间是一样的, 3600比起1040多了246%的额外开销。在极端情况下, byte[256][1], 额外开销的比例是19倍! 而在 C/C++ 中, 同样的语法却不增加额外的存储开销。

  • String: String 对象的空间随着内部字符数组的增长而增长。当然, String 类的对象有24个字节的额外开销。

    对于10字符以内的非空 String, 增加的开销比起有效载荷(每个字符2字节 + 4个字节的length), 多占用了100%到400%的内存。

对齐(Alignment)

看下面的 示例对象:

class X {                      // 8 字节-指向class定义的引用
   int a;                      // 4 字节
   byte b;                     // 1 字节
   Integer c = new Integer();  // 4 字节的引用
}

新手可能会认为, 一个X类的实例占用17字节的空间。 但由于需要对齐,也可称为补齐(padding), JVM分配的内存是8字节的整数倍, 所以占用的空间不是17字节,而是24字节。

当然,运行JOL的示例之后,会发现JVM会依次先排列 parent-class 的fields, 然后到本class的字段时,也是先排列8字节的,排完了8字节的再排4字节的field,以此类推。当然,还会加塞子_ 尽量不浪费空间。

Java内置的序列化,也会基于这个布局,带来的坑就是加字段后就不兼容了。 只加方法不固定 serialVersionUID 也出问题。 所以有点经验的都不喜欢用内置序列化,例如自定义类型存到redis时。

JOL使用示例

JOL (Java Object Layout) 是分析JVM中内存布局的小工具, 通过 Unsafe, JVMTI, 以及 Serviceability Agent (SA) 来解码实际的对象布局,占用,引用。 所以 JOL 比起基于 heap dump, 或者基于规范的其他工具来得准确。

JOL的官网地址为: http://openjdk.java.net/projects/code-tools/jol/

从中可以看到:

本文最新翻译地址: https://github.com/cncounter/translation/blob/master/tiemao_2018/37_java_object-memory_consumption/37_java_object-memory_consumption.md

翻译日期: 2019年7月4日

翻译人员: 铁锚 https://renfufei.blog.csdn.net/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK