本文共 3820 字,大约阅读时间需要 12 分钟。
作为一名技术博主,我经常需要深入理解Java对象的内存布局,尤其是面对内存溢出(OOM)问题时,能够通过分析对象结构来找到问题根源。以下将从String、数组以及常见集合的内存布局入手,结合JOL工具的分析结果,带大家一探究竟。
首先,让我们从数组的内存布局说起。数组在Java中是非常基础的数据结构,它可以存储多个元素。通过JOL工具,我们可以深入分析一个数组对象的内存布局。
以下是使用JOL工具分析一个数组的输出结果:
INFO com.flydean.CollectionSize - [B object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 22 13 07 00 (00100010 00010011 00000111 00000000) (463650) 12 4 (object header) 0f 00 00 00 (00001111 00000000 00000000 00000000) (15) 16 15 byte [B.
可以看到,数组对象的对象头大小为16字节,再加上数组里面的内容长度是15字节,再加上1位补全。最后得到的大小是32字节。这是因为数组的对象头占用了16字节,而数组本身存储的是基础类型的值,这些值在64位JVM中占用了15字节的空间。
需要注意的是,如果数组中存储的是对象类型(如引用),那么每个元素将占用4字节的空间。因此,在实际应用中,选择适当的数据类型对于内存管理至关重要。
String类在Java中是特殊的,因为它底层是由byte数组实现的。这样看起来似乎有点奇怪,但实际上这是为了提高性能而做出的优化。以下是使用JOL工具分析一个String对象的输出结果:
INFO com.flydean.CollectionSize - java.lang.String object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 77 1a 06 00 (01110111 00011010 00000110 00000000) (399991) 12 4 byte[] String.value [119, 119, 119, 46, 102, 108, 121, 100, 101, 97, 110, 46, 99, 111, 109] 16 4 int String.hash 0 20 1 byte String.coder 0 21 1 boolean String.hashIsZero false 22 2 (loss due to the next object alignment)Instance size: 24 bytesSpace losses: 0 bytes internal + 2 bytes external = 2 bytes total
从输出结果可以看出,String对象的对象头占用了12字节,接着是一个指向底层byte数组的4字节指针。再加上一些附加字段(如hash、coder和hasIsZero),最终String对象的大小为24字节。需要注意的是,这24字节不包括底层byte数组的大小,实际String的内存占用还包括底层数组的空间。
在JDK9之前,String的底层存储结构是char数组,而在JDK9之后,为了减少内存占用,底层存储结构改为byte数组。这种优化对于存储大量字符数据非常有用。
ArrayList是Java中最常用的集合之一,它基于动态数组实现。以下是使用JOL工具分析一个ArrayList对象的输出结果:
INFO com.flydean.CollectionSize - java.util.ArrayList object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 87 81 05 00 (10000111 10000001 00000101 00000000) (360839) 12 4 int AbstractList.modCount 0 16 4 int ArrayList.size 0 20 4 java.lang.Object[] ArrayList.elementData []Instance size: 24 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total
从输出结果可以看出,ArrayList对象的对象头占用了8字节,接着是modCount和size字段,各占4字节。最后是一个指向底层Object数组的指针,占用4字节。整个ArrayList对象的大小为24字节。需要注意的是,底层数组的大小并不在 ArrayList对象的大小内存中占用,而是单独计算。
由于篇幅限制,我这里不会详细分析HashMap、HashSet、LinkedList和TreeMap等集合的内存布局。但可以简单提到,这些集合的内存布局通常包括对象头、数据存储区域以及一些管理字段(如计数器)。如果对这些集合的内存布局感兴趣,可以通过JOL工具自行进行分析。
通过对String、数组和ArrayList等Java对象的内存布局分析,我们可以更好地理解它们在内存中的占用情况。选择合适的数据结构和集合类型,对于优化内存使用和解决内存溢出问题具有重要意义。
如果你对集合的内存布局感兴趣,可以继续阅读相关资料,深入探索这些集合在内存中的具体实现细节。希望本文能为你的Java内存分析之旅提供一些帮助!
转载地址:http://oiiuz.baihongyu.com/