当前位置: 首页 > 后端技术 > Java

乞丐如何节省Java内存

时间:2023-04-01 23:08:29 Java

作者:MikhailVorontsov为什么要减少内存占用本文将为您提供有关优化Java内存消耗的一般性建议。内存使用优化在Java中很重要。系统性能主要受限于内存访问性能,而不是CPU时钟速度,否则CPU制造商为什么要实现所有这些L1、L2和L3缓存?这意味着通过减少应用程序的内存占用,您可能会通过让CPU等待更少量的数据来提高程序的数据处理速度。即:节省内存提高性能!Java的内存布局方法先来回顾一下小学二年级学过的Java对象的内存布局:任何Java对象至少占用16个字节,其中12个字节被Java对象头占用。此外,所有Java对象都按8字节边界对齐。这意味着,具有2个字段的对象:int和byte:将占用17个字节(12+4+1)而不是24个字节(17个按8个字节对齐)。如果Java堆在32G以下,并且开启选项XX:+UseCompressedOops(从JDK6_u23开始默认开启UseCompressedOops),每个Object引用占用4个字节。否则,对象引用占用8个字节。所有原始数据类型都占用其确切的字节大小:byte、boolean1byteshort、char2bytesinteger、float4byteslong、double8bytes本质上,这些信息足以让Java内存优化起来。但是如果你知道Array/String数字包装器的内存消耗会更方便。最常见的Java类型内存消耗数组消耗12个字节加上它们的长度乘以它们的元素大小(当然还有8字节对齐的额外足迹)。从Java7build06开始,String包含3个字段-一个带有字符串数据的char[]int字段加上2个带有由不同算法计算的2个哈希码的字段。这意味着String本身需要12(header)+4(char[]reference)+42(int)=24字节(如您所见,这完全符合8字节对齐)。除此之外,带有String数据的char[]占用12+长度2个字节(加上对齐)。这意味着String占用36+length*2byte对齐8字节(顺便说一句,这比Java7build06之前String的内存消耗少了8字节)。数字包装器占用12个字节加上基础类型的大小。Byte/Short/Character/Integer/Long都被JDK缓存了,所以对于-128~127范围内的值,实际的内存消耗可能会更小。无论如何,这些类型可能是基于集合的应用程序中严重内存开销的来源:Byte、Boolean16bytesShort、Character16bytesInteger、Float16bytesLong、Double24bytes内存优化提示:优先使用原始类型而不是对象包装器。使用包装器类型的主要原因是JDK集合,因此请考虑使用一种原始类型集合框架,如Trove。控制您拥有的对象数量。例如,优先考虑基于数组的结构而不是基于指针的结构,例如:ArrayList/ArrayDeque/LinkedListJava内存优化示例下面是一个示例。假设您必须创建一个从int到20个字符长的字符串的映射。该地图的大小等于一百万,并且所有地图都是静态的和预定义的(例如保存在某些字典中)。第一种方法是使用标准JDK中的Map。我们粗略估算一下这个结构体的内存消耗。每个Integer占用16个字节加上4个字节用于对Integer映射的引用。每个20个字符长的String占用36+20*2=76个字节(见上面的String说明),对齐到80个字节。加上4个字节供参考。总内存消耗约为(16+4+80+4)*1M=104M。更好的方法是将字符串用StringPart1UTF-8编码替换为byte[](请参阅将字符转换为字节一文)。我们的地图将是Map。所有字符串字符都假定为ASCII集(0-127),这在大多数英语国家都是如此。byte[20]占用12(header)+20*1=32字节,这很方便地适合8字节对齐。整个Map现在将占用(16+4+32+4)*1M=56M,比前面的例子少了1.5。现在让我们使用TroveTIntObjectMap。int[]存储键值通常与JDK集合中的包装器类型相比。现在每个键将占用4个字节。总内存消耗将下降到(4+32+4)*1M=40M。最终的结构会更加复杂。所有的String值都会一个接一个地存储在byte[]中(我们仍然假设我们有一个基于文本的ASCII字符串),中间用字节0分隔。整体byte[]会占用(20+1)*1M=21M。我们的Map将存储字符串的偏移量byte[]而不是字符串本身。为此,我们将使用Trove的TIntIntMap。它将消耗(4+4)*1M=8M。此示例中的总内存消耗将为8M+21M=29M。顺便说一下,这是第一个依赖于该数据集不变性的示例。我们能取得更好的成绩吗?是的,我们可以,但要以消耗CPU为代价。明显的“优化”是将值存储在一个大字节[]中。现在我们可以将密钥存储在int[]中并使用二进制搜索来查找密钥。如果找到一个键,其索引乘以21(请记住,所有字符串的长度都相同)将为我们提供一个字节[]中的值。与hashmap情况下的查找相比,这种结构“Only”需要21M+4M(forint[])=25M,代价是查找的复杂度从O(1)到O(logN)。这是我们能做的最好的吗?不!我们忘记了所有的值都是20个字符长的,所以我们实际上不需要byte[]之间的分隔符。这意味着我们可以使用24M内存来存储我们的“地图”。与理论数据大小相比,绝对没有开销,几乎比原始解决方案(Map)所需的少4.5倍!谁告诉你Java程序消耗大量内存的?总结优先使用原始类型而不是对象包装器。使用包装器类型的主要原因是JDK集合,因此请考虑使用一种原始类型集合框架,如Trove。尽量减少您拥有的Object的数量。例如,基于数组的结构优于基于指针的结构,如ArrayList/ArrayDeque/LinkedList推荐阅读如果您想了解更多关于聪明的数据压缩算法,请阅读JonBentley的“ProgrammingPearls”(第2版)。这是非常出乎意料的算法的精彩集合。例如,在第13.8节中,作者描述了DougMcIlroy如何将75,000个单词的拼写检查器装入64KB的RAM中。那个拼写检查器在这么小的内存中保存了它需要的所有信息,而且不使用磁盘!可能还值得注意的是,《Programming Pearls》是推荐的GoogleSRE面试准备书籍之一。