在本文中,我们将介绍五种方法,这些方法可以使用有效的编码来帮助垃圾回收器减少分配和释放内存的CPU时间,并减少GC开销。 较长的GC通常会导致我们的代码在回收内存时被停止(也称为“停止世界”)。

一些背景

GC的建立是为了处理大量短期对象的分配(例如渲染网页等,其中大部分分配的对象在服务页面后就已过时)。

GC使用所谓的“年轻”来完成此工作–分配新对象的堆段。 每个对象都有一个“年龄”(放置在对象的标题位中),该年龄定义了它在没有回收的情况下“生存”了多少个集合。 一旦达到一定年龄,该对象将被复制到堆中称为“幸存者”或“旧”世代的另一部分。

该过程虽然有效,但仍然要付出代价。 能够减少临时分配的数量确实可以帮助我们提高吞吐量,尤其是在大规模应用程序中。

以下是五种我们可以编写日常代码的方法,这些代码可以提高内存效率,而不必花费大量时间或降低代码的可读性。

1.避免隐式字符串

字符串几乎是我们管理的每个数据结构不可或缺的一部分。 它们比其他原始值重得多,它们对内存使用量的影响更大。

要注意的最重要的事情之一是字符串是不可变的 。 分配后不能修改它们。 用于连接的运算符(例如“ +”)实际上分配了一个新的String,其中包含要连接的字符串的内容。 更糟糕的是,有一个隐式的StringBuilder对象被分配来实际完成组合它们的工作。

例如 -

a = a + b; // a and b are Strings

编译器在后台生成可比较的代码:

StringBuilder temp = new StringBuilder(a).
temp.append(b);
a = temp.toString(); // a new String is allocated here.// The previous “a” is now garbage.

但情况变得更糟。

让我们看这个例子–

String result = foo() + arg;
result += boo();
System.out.println(“result = “ + result);

在此示例中,我们在后台分配了3个StringBuilder-每个加号操作一个,另外两个Strings-一个用于保存第二个赋值的结果,另一个用于保存传递给print方法的字符串。 那是另外5个对象 ,否则看起来很简单。

考虑一下在现实世界中的代码场景中会发生什么,例如生成网页,使用XML或从文件中读取文本。 嵌套在循环结构中,您可能正在查看成百上千个隐式分配的对象。 尽管VM具有处理此问题的机制, 但它是有代价的 –由用户支付。

解决方案:减少这种情况的一种方法是主动使用StringBuilder分配。 下面的示例获得与上面的代码相同的结果,同时仅分配一个StringBuilder和一个String来保存最终结果,而不是原来的五个对象。

StringBuilder value = new StringBuilder(“result = “);
value.append(foo()).append(arg).append(boo());
System.out.println(value);

通过注意隐式分配Strings和StringBuilders的方式,可以从实质上减少大规模代码位置中的短期分配量。

2.计划清单的能力

诸如ArrayLists之类的动态集合是保存动态长度数据的最基本的结构之一。 ArrayList和其他集合(例如HashMaps和TreeMaps)是使用基础Object []数组实现的。 像字符串(char []数组本身包装)一样,数组也是不可变的。 显而易见的问题变成了-如果基础数组的大小是不变的,我们如何在集合中添加/放置项目? 答案也很明显–通过分配更多的数组

让我们看这个例子–

List<Item> items = new ArrayList<Item>();for (int i = 0; i < len; i++)
{Item item = readNextItem();items.add(item);
}

len的值确定循环结束后项目的最终长度。 但是,此值对于ArrayList的构造函数是未知的,该构造函数分配具有默认大小的新Object数组。 每当超出内部阵列的容量时,就会用足够长的新阵列替换内部阵列的容量,从而使以前的阵列成为垃圾。

如果要执行数千次循环,则可能会强制分配一个新数组,并多次收集前一个数组。 对于在大规模环境中运行的代码,这些分配和取消分配都从计算机的CPU周期中扣除。

解决方案:尽可能分配具有初始容量的列表和地图,如下所示:

List<MyObject> items = new ArrayList<MyObject>(len);

这样可以确保在运行时不会发生内部数组的不必要分配和取消分配,因为列表现在具有足够的容量开始。 如果您不知道确切的大小,最好对平均大小进行估算(例如1024、4096),并添加一些缓冲区以防止意外溢出。

3.使用有效的原始集合

Java编译器的当前版本通过使用“装箱”来支持具有原始键或值类型的数组或映射-将原始值包装在可由GC分配和回收的标准对象中。

这可能会带来一些负面影响 。 Java使用内部数组实现大多数集合。 对于添加到HashMap的每个键/值条目,分配一个内部对象来容纳两个值。 这在处理地图时是必不可少的,这意味着您每次将商品放入地图时都会进行额外的分配和可能的重新分配。 还可能会增加容量并不得不重新分配新的内部阵列。 当处理包含数千个或更多条目的大型地图时,这些内部分配可能会增加GC的成本。

一种非常常见的情况是在原始值(例如Id)和对象之间保留映射。 由于Java的HashMap是为保存对象类型(相对于基元)而构建的,因此这意味着映射中的每个插入都可以潜在地分配另一个对象来保存基元值(“装箱”它)。

标准的Integer.valueOf方法缓存0到255之间的值,但是对于每个大于0的数字,除了内部键/值输入对象之外,还将分配一个新对象。 这可能会使映射的GC开销增加三倍以上。 对于那些来自C ++背景的人来说,这确实是令人不安的消息,因为STL模板可以非常有效地解决此问题。

幸运的是,此问题正在Java的下一版本中进行。 在此之前,一些出色的库已经对其进行了相当有效的处理,这些库为Java的每种原始类型提供了原始树,映射和列表。 我强烈推荐Trove ,我已经使用了很长时间 ,发现它确实可以减少大规模代码中的GC开销。

4.使用流而不是内存缓冲区

我们在服务器应用程序中处理的大多数数据都是通过文件或数据从另一个Web服务或数据库通过网络流式传输给我们的。 在大多数情况下,传入的数据是序列化的,在我们开始对其进行操作之前,需要将其反序列化为Java对象。 这个阶段很容易出现大量的隐式分配

通常最简单的方法是使用ByteArrayInputStream,ByteBuffer将数据读取到内存中,然后将其传递给反序列化代码。

这可能是一个错误的举动 ,因为您需要在构造出新的对象时为整个数据分配和释放空间。 而且,由于数据的大小可能是未知的,您猜到了–您必须分配和取消分配内部byte []数组,以便在数据超出初始缓冲区的容量时容纳它们。

解决方案非常简单。 大多数持久性库(例如Java的本机序列化,Google的协议缓冲区等)都可以直接从传入的文件或网络流中反序列化数据,而不必将其保留在内存中,也不必分配新的内部字节数组来保存数据。随着数据的增长。 如果可以的话,请采用这种方法,而不是将数据加载到内存。 您的GC将感谢您。

5.汇总列表

不变性是一件美丽的事情,但是在某些大规模情况下,它可能会存在一些严重的缺点。 一种情况是在方法之间传递List对象。

从函数返回集合时,通常建议在方法内创建集合对象(例如ArrayList),将其填充并以不可变的Collection接口的形式返回。

在某些情况下这不能很好地工作 。 最引人注目的是将集合从多个方法调用中聚合到最终集合中。 虽然不变性提供了更高的清晰度,但在大规模情况下,这也可能意味着临时集合的大量分配。

在这种情况下,解决方案不是返回新的集合,而是将值聚合到单个集合中,该集合作为参数传递到这些方法中。

示例1(效率低下)–

List<Item> items = new ArrayList<Item>();for (FileData fileData : fileDatas)
{// Each invocation creates a new interim list with possible// internal interim arraysitems.addAll(readFileItem(fileData));
}

示例2 –

List<Item> items =new ArrayList<Item>(fileDatas.size() * avgFileDataSize * 1.5);for (FileData fileData : fileDatas)
{readFileItem(fileData, items); // fill items inside
}

示例2在遵守不变性规则(通常应遵守该规则)的同时,可以保存N个列表分配(以及任何临时数组分配)。 在大规模情况下,这可以为您的GC带来福音。

补充阅读

  • 字符串实习– http://plumbr.eu/blog/reducing-memory-usage-with-string-intern
  • 高效的包装器– http://vanillajava.blogspot.co.il/2013/04/low-gc-coding-efficient-listeners.html
  • 使用Trove – http://java-performance.info/primitive-types-collections-trove-library/

此帖子也可以在Speaker Deck上找到

参考:来自Takipi博客的JCG合作伙伴 Iris Shoor的 5种编码技巧以减少GC开销 。

翻译自: https://www.javacodegeeks.com/2013/07/5-coding-hacks-to-reduce-gc-overhead.html

5个编码技巧以减少GC开销相关推荐

  1. 减少GC开销的5个编码技巧

    在这篇文章中,我们来了解一下让代码变得高效的五种技巧,这些技巧可以使我们的垃圾收集器(GC)在分配内存以及释放内存上面,占用更少的CPU时间,减少GC的开销.当内存被回收的时候,GC处理很长时间经常会 ...

  2. java超出gc开销_通过这5个简单的技巧减少GC开销

    java超出gc开销 编写代码的五种简单方法,可以提高内存效率,而无需花费更多时间或降低代码可读性 垃圾回收会为您的应用程序增加多少开销? 您可能不知道确切的数字,但您确实知道总有改进的余地. 尽管自 ...

  3. 通过这5个简单的技巧减少GC开销

    编写代码的五种简单方法,可提高内存效率,而无需花费更多时间或降低代码可读性 垃圾回收会为您的应用程序增加多少开销? 您可能不知道确切的数字,但您确实知道总有改进的余地. 尽管自动GC是最有效的过程,但 ...

  4. css hack技巧_5种减少Hack的编码技巧

    css hack技巧 在本文中,我们将探讨五种方法,这些方法可以使用有效的编码来帮助垃圾回收器花费更少的CPU时间分配和释放内存,并减少GC开销. 较长的GC通常会导致我们的代码在回收内存时停止(也称 ...

  5. Java编码技巧之高效代码50例

    来自:高德技术 导读:世界上只有两种物质:高效率和低效率:世界上只有两种人:高效率的人和低效率的人.--萧伯纳 同理,世界上只有两种代码:高效代码和低效代码:世界上只有两种人:编写高效代码的人和编写低 ...

  6. java gc 次数_浅谈如何减少GC的次数

    GC会stop the world.会暂停程序的执行,带来延迟的代价.所以在开发中,我们不希望GC的次数过多. 本文将讨论如何在开发中改善各种细节,从而减少GC的次数. (1)对象不用时最好显式置为 ...

  7. java超出gc开销限制_超出了GC开销限制– Java堆分析

    java超出gc开销限制 这篇文章是我们原来的GC开销超出问题模式的延续. 正确的Java堆分析对于消除O​​utOfMemoryError:GC开销问题至关重要. 如果您不熟悉此Java HotSp ...

  8. 超出了GC开销限制– Java堆分析

    这篇文章是我们原来的GC超出限制的问题模式帖子的延续. 正确的Java堆分析对于消除O​​utOfMemoryError:GC开销问题至关重要. 如果您不熟悉此Java HotSpot 1.6错误,建 ...

  9. android编码技巧_我如何使用编码技巧使航空公司取代丢失的婴儿车

    android编码技巧 by Kristóf Litavecz 通过克里斯托夫·利塔维奇(KristófLitavecz) 我如何使用编码技巧使航空公司取代丢失的婴儿车 (How I used my ...

最新文章

  1. 关于参数类型的转换问题
  2. MATLAB 仿真分析龙门吊车
  3. iOS GPUImage之滤镜功能说明
  4. 简单理解线程同步上下文
  5. python分布式爬虫及数据存储_二十一 Python分布式爬虫打造搜索引擎Scrapy精讲—爬虫数据保存...
  6. Paxos算法是莱斯利·兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法。
  7. DDD+分布式+负载均衡+服务治理已撸!微服务架构不就这点事?
  8. Python实例讲解 -- wxpython 基本的控件 (按钮)
  9. Aligned TripletLoss
  10. PAT乙级1088 三人行 (20分)
  11. 200 行 C 代码实现插件式 NOSQL 存储服务器(一)
  12. AD18安装教程 附软件安装包和汉化包
  13. iNFTnews | 周杰伦18年前未发布的作品Demo,藏在了区块链技术里
  14. 宝塔面板搭建方维直播图文教程
  15. spring gateway route超时时间原理解析和gateway调用流程
  16. Hyper-v安装及使用详细教程
  17. HTML信件-一种奇特的实现方式
  18. 困难时拉你一把的图片_在你遇到的困难时,总会有人拉你一把、你命中有这样的贵人吗?...
  19. 广告公司给客户做的视频如何避免被外泄?
  20. 千里之行始于足下——编译器助手(binutils与elf文件)

热门文章

  1. python参数_python参数的介绍
  2. redis集群3种模式
  3. XML——流机制解析器
  4. jdk 加密_使用JDK的密码流的加密怪癖(以及该怎么做)
  5. java中list去除空值_Java –从列表中删除所有空值
  6. apigee 安装_APIGEE:用于API代理的CI / CD管道
  7. camel apache_Apache Camel 3的工作终于开始了
  8. neo4j 连接超时_Neo4j:遍历查询超时
  9. spring javaee_开发人员对Spring vs JavaEE的看法
  10. 通过示例了解Apache Ignite Baseline拓扑