文章参考了ibm的一片技术文档。具体地址
本文将为您提供 Java™ 代码内存使用情况的深入见解,包括将 int
值置入一个 Integer
对象的内存开销、对象委托的成本和不同集合类型的内存效率。您将了解到如何确定应用程序中的哪些位置效率低下,以及如何选择正确的集合来改进您的代码。
对于 Java 应用程序,用户空间是 Java 进程占用的内存,实际上包含两个池:Java 堆和本机(非 Java)堆。Java 堆的大小由 JVM 的 Java 堆设置控制:-Xms
和 -Xmx
分别设置最小和最大 Java 堆。在按照最大的大小设置分配了 Java 堆之后,剩下的用户空间就是本机堆。
java对象详解
在您的 Java 代码使用 new
操作符创建一个 Java 对象的实例时,实际上分配的数据要比您想的多得多。例如,一个 int
值与一个 Integer
对象(能包含 int
值的最小对象)的大小比率是 1:4,这个比率可能会让您感到吃惊。额外的开销源于 JVM 用于描述 Java 对象的元数据,在本例中也就是 Integer
。
根据 JVM 的版本和供应的不同,对象元数据的数量也各有不同,但其中通常包括:
- 类:一个指向类信息的指针,描述了对象类型。举例来说,对于
java.lang.Integer
对象,这是java.lang.Integer
类的一个指针。 - 标记:一组标记,描述了对象的状态,包括对象的散列码(如果有),以及对象的形状(也就是说,对象是否是数组)。
- 锁:对象的同步信息,也就是说,对象目前是否正在同步。
对象元数据后紧跟着对象数据本身,包括对象实例中存储的字段。对于 java.lang.Integer
对象,这就是一个 int
。
如果您正在运行一个 32 位 JVM,那么在创建 java.lang.Integer
对象实例时,对象的布局可能如下图所示:
如上图所示,有 128 位的数据空间用于存储 int值内的 数据,其中int数据本身占用32位,其他元数据占用了128位的其他部分。
java数组对象详解
数组对象(例如一个 int
值数组)的形状和结构与标准 Java 对象相似。主要差别在于数组对象包含说明数组大小的额外元数据。因此,数据对象的元数据包括:
- 类:一个指向类信息的指针,描述了对象类型。举例来说,对于
int
字段数组,这是int[]
类的一个指针。 - 标记:一组标记,描述了对象的状态,包括对象的散列码(如果有),以及对象的形状(也就是说,对象是否是数组)。
- 锁:对象的同步信息,也就是说,对象目前是否正在同步。
- 大小:数组的大小。
下图 展示了一个 int
数组对象的布局示例:
如上图所示,有 160 位的数据空间用于存储 int
值内的数据,上图中,int占用了32位,而数组元数据占用了 160 位的其他空间。对于 byte
、int
和 long
等原语,从内存的方面考虑,单项数组【仅包含一个元素的数组】比对应的针对单一字段的包装器对象(Byte
、Integer
或 Long
)的成本更高。
更为复杂的数据结构详解
良好的面向对象设计与编程鼓励使用封装(提供接口类来控制数据访问)和委托(使用 helper 对象来实施任务)。封装和委托会使大多数数据结构的表示形式中包含多个对象。一个简单的示例就是 java.lang.String
对象。java.lang.String
对象中的数据是一个字符数组,由管理和控制对字符数组的访问的 java.lang.String
对象封装。下图展示了一个 32 位 Java 进程的java.lang.String
对象的布局示例:
上图中,除了标准对象元数据之外,java.lang.String
对象还包含一些用于管理字符串数据的字段。通常情况下,这些字段是散列值、字符串大小计数、字符串数据偏移量和对于字符数组本身的对象引用。
这也就意味着,对于一个 8 个字符的字符串(128 位的 char
数据),需要有 256 位的数据用于字符数组,224 位的数据用于管理该数组的 java.lang.String
对象,因此为了表示 128 位(16 个字节)的数据,总共需要占用 480 位(60 字节)。开销比例为 3.75:1。总体而言,数据结构越是复杂,开销就越高。
32位和64位java对象
前段时间面试tx的时候就被面试官问到这个问题。当时就回答,没有了解过64位机的对象。现在补补知识。
——————————————————————————粉葛————————————————————————————
按照文章中说的,在32位和64位机器上,int、long等的长度没有变化,还是分别为32和64。但是占用的总内存增大了。这部分增大的内存是用来存放元信息的,比如图中的classpointer、flags等。
——————————————————————————粉葛————————————————————————————
之前的示例中的对象大小和开销适用于 32 位 Java 进程,64 位处理器的内存可寻址能力比 32 位处理器高得多。对于 64 位进程,Java 对象中的某些数据字段的大小(特别是对象元数据或者表示另一个对象的任何字段)也需要增加到 64 位。其他数据字段类型(例如 int
、byte
和 long
)的大小不会更改。下图展示了一个 64 位 Integer
对象和一个 int
数组的布局:
对于一个 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% 之多。
java集合的内存使用
在大多数应用程序中,大量数据都是使用核心 Java API 提供的标准 Java Collections 类来存储和管理的。如果内存占用对于应用程序极为重要,那么就非常有必要了解各集合提供的功能以及相关的内存开销。总体而言,集合功能的级别越高,内存开销就越高,因此使用提供的功能多于您需要的功能的集合类型会带来不必要的额外内存开销。
java集合汇总
集合 | 性能 | 默认容量 | 空时的大小 | 10K 条目的开销 | 准确设置大小? | 扩展算法 |
---|---|---|---|---|---|---|
HashSet | O(1) | 16 | 144 | 360K | 否 | x2 |
HashMap | O(1) | 16 | 128 | 360K | 否 | x2 |
Hashtable | O(1) | 11 | 104 | 360K | 否 | x2+1 |
LinkedList | O(n) | 1 | 48 | 240K | 是 | +1 |
ArrayList | O(n) | 10 | 88 | 40K | 否 | x1.5 |
StringBuffer | O(1) | 16 | 72 | 24 | 否 | x2 |