java 代码获取堆_从Java代码到Java堆

尽管优化应用程序代码的内存使用率的主题不是新话题,但它并不是众所周知的。 本文简要介绍了Java进程的内存使用情况,然后深入研究了您编写的Java代码的内存使用情况。 最后,它展示了使应用程序代码的存储效率更高的方法,特别是在使用Java集合(例如HashMapArrayList

背景:Java进程的内存使用情况

通过在命令行上执行java或启动某些基于Java的中间件来运行Java应用程序时,Java运行时会创建一个操作系统进程,就像运行基于C的程序一样。 实际上,大多数JVM基本上都是用C或C ++编写的。 作为操作系统进程,Java运行时与任何其他进程一样在内存方面面临相同的限制:体系结构提供的可寻址性和操作系统提供的用户空间。

该体系结构提供的内存可寻址性取决于处理器的位大小-例如32位或64位,或者在大型机的情况下为31位。 进程可以处理的位数决定了处理器可以寻址的内存范围:32位提供2 ^ 32的可寻址范围,即4,294,967,296位或4GB。 64位处理器的可寻址范围要大得多:2 ^ 64是18,446,744,073,709,551,616或16艾字节。

操作系统本身将处理器体系结构提供的某些可寻址范围用于其内核,(对于使用C或C ++编写的JVM)将其用于C运行时。 OS和C运行时使用的内存量取决于所使用的OS,但通常很重要:Windows的默认使用量为2GB。 剩余的可寻址空间(称为用户空间 )是正在运行的实际进程可用的内存。

对于Java应用程序,用户空间是Java进程使用的内存,实际上由两个池组成:Java堆和本机 (非Java)堆。 Java堆的大小由JVM的Java堆设置控制: -Xms-Xmx设置最小和最大Java堆。 本机堆是在以最大大小设置分配Java堆之后剩余的用户空间。 图1显示了32位Java进程的外观示例:

图1. 32位Java进程的示例内存布局
32位Java进程的内存布局示例视图

图1中 ,OS和C运行时使用了4GB可寻址范围中的大约1GB,Java堆使用了将近2GB,而本机堆使用了其余空间。 请注意,JVM本身使用内存(与OS内核和C运行时相同),并且JVM使用的内存是本机堆的子集。

Java对象的剖析

当您的Java代码使用new运算符创建Java对象的实例时,分配的数据比您预期的要多得多。 例如,您可能会惊讶地发现一个int值与一个Integer对象(可以容纳int值的最小对象)的大小比通常为1:4。 额外的开销是JVM用于描述Java对象的元数据,在本例中为Integer

对象元数据的数量随JVM版本和供应商的不同而不同,但通常包括:

  • Class :指向类信息的指针,它描述对象的类型。 例如,对于java.lang.Integer对象,这是指向java.lang.Integer类的指针。
  • 标志 :描述对象状态的标志的集合,包括对象的哈希码(如果有的话)以及对象的形状 (即对象是否为数组)。
  • Lock :对象的同步信息,即对象当前是否同步。

然后,对象元数据后跟对象数据本身,该对象数据由存储在对象实例中的字段组成。 对于java.lang.Integer对象,这是一个int

因此,当您在运行32位JVM时创建java.lang.Integer对象的实例时,该对象的布局可能如图2所示:

图2. 32位Java流程的java.lang.Integer对象的示例布局
32位Java进程的java.lang.Integer对象的示例布局

如图2所示,因为对象元数据使用了这128位中的其余部分,所以将128位数据用于存储int值中的32位数据。

Java数组对象的剖析

数组对象(例如int值的数组)的形状和结构与标准Java对象的形状和结构相似。 主要区别在于,数组对象还有一条额外的元数据,它表示数组的大小。 因此,数组对象的元数据包括:

  • Class :指向类信息的指针,它描述对象的类型。 对于int字段数组,这是指向int[]类的指针。
  • 标志 :描述对象状态的标志的集合,包括对象的哈希码(如果有的话)以及对象的形状(即对象是否为数组)。
  • Lock :对象的同步信息,即对象当前是否同步。
  • Size :数组的大小。

图3显示了一个int数组对象的示例布局:

图3. 32位Java流程的int数组对象的示例布局
32位Java进程的int数组对象的示例布局

图3中 ,因为数组元数据使用这160位中的其余部分,所以160位数据将32位数据存储在int值中。 对于诸如byteintlong基元,就内存而言,单入口数组比单字段对应的包装对象( ByteIntegerLong )昂贵。

更加复杂的数据结构剖析

良好的面向对象设计和编程鼓励使用封装 (提供控制数据访问的接口类)和委派 (使用帮助对象执行任务)。 封装和委派使大多数数据结构的表示涉及多个对象。 一个简单的示例是java.lang.String对象。 java.lang.String对象中的数据是由java.lang.String对象封装的字符数组,该对象管理和控制对字符数组的访问。 32位Java进程的java.lang.String对象的布局可能类似于图4:

图4. 32位Java流程的java.lang.String对象的示例布局
32位Java进程的java.lang.String对象的示例布局

如图4所示,除了标准对象元数据外, java.lang.String对象还包含一些用于管理字符串数据的字段。 通常,这些字段是哈希值,字符串大小的计数,字符串数据的偏移量以及对字符数组本身的对象引用。

这意味着要具有8个字符的字符串(128位char数据),则256位数据用于字符数组,而224位数据用于管理该字符的java.lang.String对象,总共480个位(60字节)代表128位(16字节)数据。 这是3.75:1的间接费用比率。

通常,数据结构越复杂,其开销就越大。 下一节将对此进行详细讨论。

32位和64位Java对象

前面示例中对象的大小和开销适用于32位Java进程。 正如您从“ 背景:Java进程部分的内存使用情况”中了解到的那样,64位处理器的内存可寻址性级别比32位处理器高得多。 使用64位进程时,Java对象中某些数据字段的大小(特别是对象元数据和引用另一个对象的任何字段)的大小也需要增加到64位。 其他数据字段类型(例如intbytelong的大小不会改变。 图5显示了一个64位Integer对象和一个int数组的布局:

图5.用于64位Java进程的java.lang.Integer对象和int数组的示例布局
用于64位Java进程的java.lang.Integer对象和int数组的示例布局

图5显示,对于64位Integer对象,现在使用224位数据存储用于int字段的32位-开销比为7:1。 对于64位单元素int数组,将使用288位数据存储32位int条目-开销为9:1。 此操作对实际应用程序的影响是,将以前运行在32位Java运行时上的应用程序移至64位Java运行时时,其Java堆内存使用量显着增加。 通常,增加量约为原始堆大小的70%。 例如,使用1GB Java堆和32位Java运行时的Java应用程序通常将使用1.7GB Java堆和64位Java运行时。

请注意,这种内存增加并不限于Java堆。 本机堆内存区域的使用量也会增加,有时会增加90%。

表1显示了当应用程序以32位和64位模式运行时对象和数组的字段大小:

表1. 32位和64位Java运行时的对象中的字段大小
栏位类型 字段大小(位)
目的 数组
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 64 64 64 64
double 64 64 64 64
对象字段 32 64(32 *) 32 64(32 *)
对象元数据 32 64(32 *) 32 64(32 *)

*通过压缩参考或压缩OOP技术,对象字段和用于每个对象元数据条目的数据的大小可以减少到32位。

压缩参考和压缩普通对象指针(OOP)

IBM和Oracle JVM都分别通过压缩引用( -Xcompressedrefs )和压缩-Xcompressedrefs-XX:+UseCompressedOops )选项提供了对象引用压缩功能。 使用这些选项可以将对象字段和对象元数据值存储为32位而不是64位。 当应用程序从32位Java运行时移动到64位Java运行时时,这样做的效果是使Java堆内存增加了70%。 注意,这些选项对本机堆的内存使用没有影响; 使用64位Java运行时,它仍然比使用32位Java运行时更高。

Java集合的内存使用情况

在大多数应用程序中,使用作为核心Java API一部分提供的标准Java Collections类来存储和管理大量数据。 如果内存占用空间优化对您的应用程序很重要,那么了解每个集合提供的功能以及相关的内存开销特别有用。 通常,集合功能功能的级别越高,其内存开销就越高—因此,使用提供了比您所需功能更多的功能的集合类型将导致不必要的额外内存开销。

一些常用的集合是:

除了HashSet ,此列表按功能和内存开销的降序排列。 (作为HashMap对象的包装器的HashSet实际上提供的功能比HashMap少,但要稍大一些。)

Java集合: HashSet

HashSetSet接口的实现。 Java Platform SE 6 API文档将HashSet描述为:

不包含重复元素的集合。 更正式地讲,集合不包含元素对e1和e2,使得e1.equals(e2)最多包含一个空元素。 顾名思义,此接口对数学集合抽象进行建模。

HashSet的功能比HashMap少,因为它不能包含多个空条目,并且不能有重复的条目。 该实现是HashMap的包装, HashSet对象管理允许放入HashMap对象的内容。 限制HashMap功能的附加功能意味着HashSet的内存开销略高。

图6显示了32位Java运行时上HashSet的布局和内存使用情况:

图6. HashSet在32位Java运行时上的内存使用情况和布局
HashSet在32位Java运行时上的内存使用情况和布局

图6显示了java.util.HashSet对象的浅堆 (单个对象的内存使用)(以字节为单位)以及保留堆 (单个对象及其子对象的内存使用)(以字节为单位)。 浅堆大小为16个字节,保留堆大小为144个字节。 创建HashSet ,其默认容量 (可放入集合中的条目数)为16个条目。 当以默认容量创建HashSet且未将任何条目放入集合中时,它占用144个字节。 这比HashMap的内存使用量多了16个字节。 表2显示了HashSet的属性:

表2. HashSet属性
属性 描述
默认容量 16条目
空尺寸 144字节
高架 16个字节加上HashMap开销
1万个收藏的开销 16个字节加上HashMap开销
搜索/插入/删除效果 O(1)—花费的时间是恒定时间,与元素的数量无关(假设没有哈希冲突)

Java集合: HashMap

HashMapMap接口的实现。 Java Platform SE 6 API文档将HashMap描述为:

将键映射到值的对象。 映射不能包含重复的键; 每个键最多可以映射到一个值。

HashMap提供了一种存储键/值对的方法,该方法使用哈希函数将键转换为存储键/值对的集合中的索引。 这样可以快速访问数据位置。 空条目和重复条目是允许的; 这样, HashMapHashSet的简化。

HashMap的实现是作为HashMap$Entry对象的数组。 图7显示了32位Java运行时上HashMap的内存使用情况和布局:

图7. 32位Java运行时上的HashMap内存使用情况和布局
HashMap在32位Java运行时上的内存使用情况和布局

如图7所示,创建HashMap时,结果是一个HashMap对象和一个HashMap$Entry对象数组,其默认容量为16个条目。 当HashMap完全为空时,它的大小为128个字节。 插入到HashMap中的任何键/值对都由HashMap$Entry对象包装,该对象本身有一些开销。

HashMap$Entry对象的大多数实现都包含以下字段:

  • int KeyHash
  • Object next
  • Object key
  • Object value

一个32字节的HashMap$Entry对象管理放入集合中的数据的键/值对。 这意味着HashMap的总开销由HashMap对象, HashMap$Entry数组条目和每个条目的HashMap$Entry对象组成。 这可以用以下公式表示:

HashMap对象+数组对象开销+(条目数*( HashMap$Entry数组条目+ HashMap$Entry对象))

对于10,000个条目的HashMap ,仅HashMapHashMap$Entry数组和HashMap$Entry对象的开销约为360K。 这是在考虑键的大小和要存储的值之前。

表3显示了HashMap的属性:

表3. HashMap属性
属性 描述
默认容量 16条目
空尺寸 128字节
高架 64字节加上每个条目36字节
1万个收藏的开销 约36万
搜索/插入/删除效果 O(1)—花费的时间是恒定时间,与元素的数量无关(假设没有哈希冲突)

Java集合: Hashtable

HashMap一样, HashtableMap接口的实现。 Java平台SE 6 API文档对Hashtable的描述为:

此类实现一个哈希表,该哈希表将键映射到值。 任何非空对象都可以用作键或值。

HashtableHashMap非常相似,但是有两个限制。 它不能接受键或值条目的空值,并且它是一个同步的集合。 相反, HashMap可以接受空值,并且不同步,但是可以使用Collections.synchronizedMap()方法使其同步。

Hashtable的实现(也类似于HashMap的实现)是作为条目对象的数组,在本例中为Hashtable$Entry对象。 图8显示了32位Java运行时上的Hashtable表的内存使用情况和布局:

图8. 32位Java运行时上的Hashtable内存使用情况和布局
Hashtable在32位Java运行时上的内存使用情况和布局

图8显示了创建Hashtable表时,结果是一个Hashtable对象,该Hashtable对象使用40个字节的内存以及一个默认容量为11个条目的Hashtable$entry数组,一个空的Hashtable的大小总计104个字节。

Hashtable$Entry有效地存储与HashMap相同的数据:

  • int KeyHash
  • Object next
  • Object key
  • Object value

这意味着, Hashtable$Entry对象也是在键/值输入32个字节Hashtable ,并且计算为Hashtable一个10K条目集合(约360K)的开销,并且大小类似于HashMap的。

表4显示了Hashtable的属性:

表4. Hashtable表的属性
属性 描述
默认容量 11条目
空尺寸 104字节
高架 56个字节,每个条目36个字节
1万个收藏的开销 约36万
搜索/插入/删除效果 O(1)—花费的时间是恒定时间,与元素的数量无关(假设没有哈希冲突)

如您所见, Hashtable默认容量比HashMap略小(11 vs. 16)。 否则,主要区别在于Hashtable不能接受空键和值,以及它的默认同步(可能不需要),这会降低集合的性能。

Java集合: LinkedList

LinkedListList接口的链表实现。 Java Platform SE 6 API文档将LinkedList描述为:

有序集合(也称为序列)。 该界面的用户可以精确控制列表中每个元素的插入位置。 用户可以通过其整数索引(列表中的位置)访问元素,并在列表中搜索元素。 与集合不同,列表通常允许重复的元素。

该实现是LinkedList$Entry对象的链接列表。 图9显示了32位Java运行时上LinkedList的内存使用情况和布局:

图9. 32位Java运行时上的LinkedList内存使用情况和布局
32位Java运行时上的内存使用率和LinkedList的布局

图9显示创建LinkedList时,结果是一个LinkedList对象,它使用24个字节的内存以及一个LinkedList$Entry对象,一个空的LinkedList总共需要48个字节的内存。

链接列表的优点之一是它们的大小准确,不需要调整大小。 默认容量实际上是一个条目,并且随着增加或删除更多条目而动态增加和缩小。 每个LinkedList$Entry对象的开销仍然LinkedList$Entry

  • Object previous
  • Object next
  • Object value

但这比HashMapHashtable的开销要小,因为链接列表仅存储单个条目,而不存储键/值对,并且不需要存储哈希值,因为不使用基于数组的查找。 不利的一面是,对链表的查找会慢得多,因为必须遍历链表才能找到正确的条目。 对于大型链表,这可能会导致较长的查找时间。

表5显示了LinkedList的属性:

表5. LinkedList属性
属性 描述
默认容量 1项
空尺寸 48字节
高架 24个字节,每个条目加上24个字节
1万个收藏的开销 〜240K
搜索/插入/删除效果 O(n)-花费的时间与元素数线性相关

Java集合: ArrayList

ArrayListList接口的可调整大小的数组实现。 Java Platform SE 6 API文档将ArrayList描述为:

有序集合(也称为序列)。 该界面的用户可以精确控制列表中每个元素的插入位置。 用户可以通过其整数索引(列表中的位置)访问元素,并在列表中搜索元素。 与集合不同,列表通常允许重复的元素。

LinkedList不同, ArrayList是使用Object数组实现的。 图10显示了32位Java运行时上的ArrayList的内存使用情况和布局:

图10. 32位Java运行时上的ArrayList内存使用情况和布局
32位Java运行时上的ArrayList的内存使用情况和布局

图10显示创建一个ArrayList时,结果是一个使用32字节内存的ArrayList对象,以及一个默认大小为10的Object数组,一个空ArrayList总共有88字节内存,这意味着ArrayList不是大小正确,因此具有默认容量,恰好是10个条目。

表6显示了ArrayList属性:

表6. ArrayList属性
属性 描述
默认容量 10
空尺寸 88字节
高架 48个字节,每个条目4个字节
10K收集的开销 约40K
搜索/插入/删除效果 O(n)-花费的时间与元素数成线性关系

其他类型的“收藏”

除标准集合外, StringBuffer还可以被视为集合,因为它可以管理字符数据,并且在结构和功能上与其他集合相似。 Java Platform SE 6 API文档将StringBuffer描述为:

线程安全的可变字符序列。每个字符串缓冲区都有一个容量。 只要字符串缓冲区中包含的字符序列的长度不超过容量,就不必分配新的内部缓冲区数组。 如果内部缓冲区溢出,则会自动变大。

StringBuffer的实现是作为char数组。 图11显示了32位Java运行时上StringBuffer的内存使用情况和布局:

图11. 32位Java运行时上的StringBuffer内存使用情况和布局
32位Java运行时上的StringBuffer的内存使用和布局

图11显示了创建StringBuffer时,结果是一个使用24字节内存的StringBuffer对象,以及一个默认大小为16的字符数组,一个空StringBuffer总共有72字节数据。

像集合一样, StringBuffer具有默认容量和调整大小的机制。 表7显示了StringBuffer的属性:

表7. StringBuffer属性
属性 描述
默认容量 16
空尺寸 72字节
高架 24字节
10K收集的开销 24字节
搜索/插入/删除效果 不适用

收藏集中的空白空间

具有给定数量的对象的各种集合的开销并不是整个内存开销的故事。 前述示例中的测量结果假设集合的大小已正确确定。 但是对于大多数收藏来说,这不太可能是正确的。 大多数集合都是以给定的初始容量创建的,并将数据放入集合中。 这意味着集合的容量通常大于集合中存储的数据,这会带来额外的开销。

考虑一个StringBuffer的示例。 它的默认容量是16个字符条目,大小为72个字节。 最初,没有数据存储在72个字节中。 如果将一些字符放入字符数组(例如"MY STRING" ,则将在16个字符的数组中存储9个字符。 图12显示了在32位Java运行时上包含"MY STRING"StringBuffer的内存使用情况:

图12. 32位Java运行时上包含"MY STRING"StringBuffer内存使用情况
32位Java运行时上包含“ MY STRING”的StringBuffer的内存使用情况

如图12所示,未使用数组中的7个附加字符条目,但它们正在占用内存-在这种情况下,附加开销为112个字节。 对于此集合,您有9个条目,容量为16,这使您的填充率为0.56。 集合的填充率越低,由于备用容量导致的开销就越大。

扩展和调整集合大小

集合达到其容量并请求将其他条目放入集合后,将调整集合的大小并对其进行扩展以容纳新条目。 这样可以增加容量,但通常会降低填充率,并增加内存开销。

集合之间使用的扩展算法不同,但是一种常见的方法是使集合的容量增加一倍。 这是StringBuffer采取的方法。 对于前面的示例的StringBuffer ,如果要在缓冲区中追加" OF TEXT"以产生"MY STRING OF TEXT" ,则需要扩展集合,因为新的字符集合在当前容量下有17个条目之16。图13显示了产生的内存使用情况:

图13.在32位Java运行时上包含"MY STRING OF TEXT"StringBuffer内存使用情况
32位Java运行时上包含“ MY STRING OF TEXT”的StringBuffer的内存使用情况

现在, 如图13所示,您有一个32个条目的字符数组和17个已使用的条目,填充率为0.53。 填充率并没有显着下降,但是您现在有240字节的备用容量开销。

对于较小的字符串和集合,低填充率和备用容量的开​​销似乎不是太大的问题,但是在更大的尺寸下它们变得更加明显和昂贵。 例如,如果您创建一个仅包含16MB数据的StringBuffer ,(默认情况下)它将使用字符数组,该字符数组的大小可容纳32MB数据,从而以备用容量的形式增加16MB的额外开销。

Java集合:摘要

表8总结了集合的属性:

表8.集合属性摘要
采集 性能 默认容量 空尺寸 1万个入门开销 尺寸正确吗? 扩展算法
HashSet O(1) 16 144 360K 没有 2倍
HashMap O(1) 16 128 360K 没有 2倍
Hashtable O(1) 11 104 360K 没有 x2 + 1
LinkedList 上) 1个 48 24万 +1
ArrayList 上) 10 88 40K 没有 x1.5
StringBuffer O(1) 16 72 24 没有 2倍

Hash集合的性能比List的任何一个都要好得多,但每个条目的成本要高得多。 由于访问性能的原因,如果要创建大型集合(例如,实现高速缓存),则最好使用基于Hash的集合,而无需考虑额外的开销。

对于访问性能影响较小的较小集合,可以使用ListArrayListLinkedList集合的性能大致相同,但是它们的内存占用量有所不同: ArrayList的每个条目大小比LinkedList小得多,但大小不准确。 ArrayListLinkedList是要使用的List的正确实现方式,取决于List长度的可预测性。 如果长度未知,则LinkedList可能是正确的选择,因为该集合将包含较少的空白空间。 如果知道大小,则ArrayList内存开销会少得多。

选择正确的收集类型使您能够在收集性能和内存占用之间选择适当的平衡。 此外,您可以通过正确调整集合的大小来最大程度地减少内存占用,以最大程度地提高填充率并最大程度地减少未使用的空间。

使用中的集合:PlantsByWebSphere和WebSphere Application Server版本7

表8中 ,创建一个10,000个基于Hash的集合的开销显示为360K。 鉴于复杂的Java应用程序与千兆字节大小的Java堆一起运行并不少见,因此这似乎并不会带来很大的开销-除非当然要使用大量的集合。

表9显示了WebSphere®Application Server版本7随附的PlantsByWebSphere示例应用程序在五用户负载测试下运行时,收集对象的使用作为206MB Java堆使用的一部分:

表9. PlantsByWebSphere在WebSphere Application Server v7上的集合用法
收集类型 实例数 收集总开销(MB)
Hashtable 262,234 26.5
WeakHashMap 19562 12.6
HashMap 10,600 2.3
ArrayList 9,530 0.3
HashSet 1,551 1.0
Vector 1,271 0.04
LinkedList 1,148 0.1
TreeMap 299 0.03
306,195 42.9

表9中可以看到,正在使用300,000多个不同的集合,并且集合本身(不计算它们包含的数据)占206MB Java堆使用量的42.9MB(21%)。 这意味着,如果您更改集合类型或确保集合的大小更准确,则可以节省大量潜在的内存。

使用Memory Analyzer寻找低填充率

作为IBM Support Assistant的一部分提供的IBM Java监视和诊断工具-内存分析器工具(内存分析器)可以分析Java集合的内存使用情况(请参阅参考资料 )。 它的功能包括分析填充率和集合大小。 您可以使用此分析来确定可以进行优化的所有集合。

Memory Analyzer中的收集分析功能位于Open Query Browser-> Java Collections菜单下,如图14所示:

图14.在内存分析器中分析Java集合的填充率
在内存分析器中分析Java集合的填充率

图14中选择的“集合填充率”查询对于识别比当前所需的要大得多的集合最有用。 您可以为此查询指定许多选项,包括:

  • objects :您感兴趣的对象(集合)的类型
  • segments :填充率范围可将对象分组

将object选项设置为“ java.util.Hashtable”并将segments选项设置为“ 10”来运行查询,将产生如图15所示的输出:

图15.在内存分析器中分析Hashtable的填充率
内存分析器中哈希表的填充率分析

图15显示了262,234个java.util.Hashtable实例java.util.Hashtable 127,016个(48.4%)是完全空的,并且几乎所有实例都只有少量的条目。

然后可以通过选择结果表的一行并右键单击以选择列表对象->具有传入引用以查看哪些对象拥有这些集合,或右键单击以选择哪些对象拥有这些集合,或单击列表对象->具有传出引用以查看这些集合中的内容来标识这些集合。集合。 图16显示了查看空Hashtable的传入引用并扩展几个条目的结果:

图16.在内存分析器中对空Hashtable的传入引用的分析
在内存分析器中分析对空哈希表的传入引用

图16显示了一些空的Hashtablejavax.management.remote.rmi.NoCallStackClassLoader代码拥有。

通过查看Memory Analyzer左侧面板中的Attributes视图,您可以看到有关Hashtable本身的特定详细信息,如图17所示:

Figure 17. Inspection of the empty Hashtable in Memory Analyzer
Inspection of the empty Hashtable in Memory Analyzer

Figure 17 shows that the Hashtable has a size of 11 (the default size) and that it is completely empty.

For the javax.management.remote.rmi.NoCallStackClassLoader code, it might be possible to optimize the collection usage by:

  • Lazily allocating the Hashtable : If it is common for the Hashtable to be empty, then it may make sense for the Hashtable to be allocated only when there is data to store inside it.
  • Allocating the Hashtable to an accurate size: Because the default size has been used, it's possible that a more accurate initial size could be used.

Whether either or both of these optimizations are applicable depends on how the code is commonly used, and what data is commonly stored inside it.

Empty collections in the PlantsByWebSphere example

Table 10 shows the result of analyzing collections in the PlantsByWebSphere example to identifying those that are empty:

Table 10. Empty-collection usage by PlantsByWebSphere on WebSphere Application Server v7
Collection type Number of instances Empty instances % Empty
Hashtable 262,234 127,016 48.4
WeakHashMap 19562 19,465 99.5
HashMap 10,600 7,599 71.7
ArrayList 9,530 4,588 48.1
HashSet 1,551 866 55.8
Vector 1,271 622 48.9
Total 304,748 160,156 52.6

Table 10 shows that on average, over 50 percent of the collections are empty, implying that significant memory-footprint savings could be gained by optimization of collection usage. It could be applied to various levels of the application: in the PlantsByWebSphere example code, in the WebSphere Application Server, and in the Java collections classes themselves.

Between WebSphere Application Server version 7 and version 8, some work has been done to improve memory efficiency in the Java collections and middleware layers. For example, a large percentage of the overhead of instances of java.util.WeahHashMap is due to the fact that it contains an instance of java.lang.ref.ReferenceQueue to handle the weak references. Figure 18 shows the memory layout of a WeakHashMap for a 32-bit Java runtime:

Figure 18. Memory layout of a WeakHashMap for a 32-bit Java runtime
Memory layout of a WeakHashMap for a 32-bit Java runtime

Figure 18 shows that the ReferenceQueue object is responsible for retaining 560 bytes' worth of data, even if the WeakHashMap is empty and ReferenceQueue is therefore not required. For the PlantsByWebSphere example case with 19,465 empty WeakHashMap s, the ReferenceQueue objects are adding an additional 10.9MB of data that is not required. In WebSphere Application Server version 8 and the Java 7 release of the IBM Java runtimes, the WeakHashMap has undergone some optimization: It contains a ReferenceQueue , which in turn contains an array of Reference objects. That array has been changed to be allocated lazily — that is, only when objects are added to the ReferenceQueue .

结论

A large and perhaps surprising number of collections exist in any given application, and more so for complex applications. Use of a large number of collections often provides scope for achieving sometimes significant memory-footprint savings by selecting the right collection, sizing it correctly, and potentially by allocating it lazily. These decisions are best made during design and development, but you can also use the Memory Analyzer tool to analyze your existing applications for potential memory-footprint optimization.


翻译自: https://www.ibm.com/developerworks/java/library/j-codetoheap/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值