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

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

背景

GC用来处理大量的短期的对象的分配(试想打开一个web页面,一旦页面被加载之后,被分配内存的大部分对象都会被废弃)。

GC使用一个被称作”新生代”堆空间来完成这件事情。”新生代”是用来存放新建对象的堆内存。每一个对象都有一个”age”(存储在对象的头信息中),用来定义存放很多没有被回收的垃圾集合。一旦一个确定的”age”到达,对象就会被复制到堆中的另一块空间,这个空间被称作”幸存者空间”或者”老年代空间”。(译者注:实际上幸存者空间位于新生代空间中,原文有误,不过这里暂时按照原文来翻译,更详细的内容请点击成为JavaGC专家Part I — 深入浅出Java垃圾回收机制)

虽然这样很有效,但是还是有很大代价的。减少临时分配的数量确实可以帮助我们增加吞吐量,尤其是在大规模数据的环境下,或者资源有限制的app中。

下面的五种代码方式可以更加有效的利用内存,并且不需要花费很多的时间,也不会降低代码可读性。

1、避免隐式的String字符串

String字符串是我们管理的每一个数据结构中不可分割的一部分。它们在被分配好了之后不可以被修改。比如”+”操作就会分配一个链接两个字符串的新的字符串。更糟糕的是,这里分配了一个隐式的StringBuilder对象来链接两个String字符串。

例如:

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

编译器在背后就会生成这样的一段儿代码:

1
2
3
4
StringBuilder temp = new StringBuilder(a).
temp.append(b);
a = temp.toString(); // 一个新的 String 对象被分配
// 第一个对象 “a” 现在可以说是垃圾了

它变得更糟糕了。

让我们来看这个例子:

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

在这个例子中,背后有三个StringBuilders 对象被分配 – 每一个都是”+”的操作所产生,和两个额外的String对象,一个持有第二次分配的result,另一个是传入到print方法的String参数,在看似非常简单的一段语句中有5个额外的对象。

试想一下在实际的代码场景中会发生什么,例如,通过xml或者文件中的文本信息生成一个web页面的过程。在嵌套循环结构,你将会发现有成百上千的对象被隐式的分配了。尽管VM有处理这些垃圾的机制,但还是有很大代价的 – 代价也许由你的用户来承担。

解决方案:

减少垃圾对象的一种方式就是善于使用StringBuilder 来建对象,下面的例子实现了与上面相同的功能,然而仅仅生成了一个StringBuilder 对象,和一个存储最终result 的String对象。

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

通过留心String和StringBuilder被隐式分配的可能,可以减少分配的短期的对象的数量,尤其在有大量代码的位置。

2、计划好List的容量

像ArrayList这样的动态集合用来存储一些长度可变化数据的基本结构。ArrayList和一些其他的集合(如HashMap、TreeMap),底层都是通过使用Object[]数组来实现的。而String(它们自己包装在char[]数组中),char数组的大小是不变的。那么问题就出现了,如果它们的大小是不变的,我们怎么能放item记录到集合中去呢?答案显而易见:分配更多的数组。

看下面的例子:

1
2
3
4
5
6
7
List<Item> items = new ArrayList<Item>();
  
for (int i = 0; i < len; i++)
{
Item item = readNextItem();
items.add(item);
}

len的值决定了循环结束时items 最终的大小。然而,最初,ArrayList的构造器并不知道这个值的大小,构造器会分配一个默认的Object数组的大小。一旦内部数组溢出,它就会被一个新的、并且足够大的数组代替,这就使之前分配的数组成为了垃圾。

如果执行数千次的循环,那么就会进行更多次数的新数组分配操作,以及更多次数的旧数组回收操作。对于在大规模环境下运行的代码,这些分配和释放的操作应该尽可能从CPU周期中剔除。

解决方案:

无论什么时候,尽可能的给List或者Map分配一个初始容量,就像这样:

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

因为List初始化,有足够的容量,所有这样可以减少内部数组在运行时不必要的分配和释放。如果你不知道确定的大小,最好估算一下这个值的平均值,添加一些缓冲,防止意外溢出。

3、使用高效的含有原始类型的集合

当前版本的Java编译器对于含有基本数据类型的键的数组以及Map的支持,是通过“装箱”来实现的 – 自动装箱就是将原始数据装入一个对应的对象中,这个对象可被GC分配和回收。

这个会有一些负面的影响。Java可以通过使用内部数组实现大多数的集合。对于每一条被添加到HashMap中的key/value记录,都会分配一个存储key和value的内部对象。当处理map的时候非常可怕,这意味着,每当你放一条记录到map中的时候,就会有一次额外的分配和释放操作发生。这很可能导致数量过大,而不得不重新分配新的内部数组。当处理有成百上千条甚至更多记录的Map时,这些内部分配的操作将会使GC的成本增加。

一种常见的情况就是保存一个原始类型(如id)和一个对象之间的映射。由于Java的HashMap设计只能包含对象类型(而非原始类型),这意味着,每个map的插入操作都可能分配一个额外的对象来存储原始类型(即装箱)。

Integer.valueOf 方法缓存在-128 – 127之间的数值,但是对于范围之外的每一个数值,除了内部的key/value记录对象之外,一个新的对象也将会分配。这很可能超过了GC对于map三倍的开销。对于一个C++开发者来说,这真是让人不安的消息,在C++中,STL 模板可以非常高效地解决这样的问题。

很幸运,这个问题将会在Java的下一个版本得到解决。到那时,这将会被一些提供基本的树形结构(Tree)、映射(Map),以及List等Java的基本类型的库迅速处理。我强力推荐Trove,我已经使用很长时间了,并且它在处理大规模的代码时真的可以减小GC的开销。

4、使用数据流(Streams)代替内存缓冲区(in-memory buffers)

在服务器应用程序中,我们操作的大多数的数据都是以文件或者是来自另一个web服务器或DB的网络数据流的形式呈现给我们。大多数情况下,传入的数据都是序列化的形式,在我们使用它们之前需要被反序列化成Java对象。这个过程非常容易产生大量的隐式分配。

最简单的做法就是通过ByteArrayInputStream,ByteBuffer 把数据读入内存中,然后再进行反序列化。

这是一个糟糕的举动,因为完整的数据在构造新的对象的时候,你需要为其分配空间,然后立刻又释放空间。并且,由于数据的大小你又不知道,你只能猜测 – 当超过初始化容量的时候,不得不分配和释放byte[]数组来存储数据。

解决方案非常简单。像Java自带的序列化工具以及Google的Protocol Buffers等,它们可以将来自于文件或网络流的数据进行反序列化,而不需要保存到内存中,也不需要分配新的byte数组来容纳增长的数据。如果可以的话,你可以将这种方法和加载数据到内存的方法比较一下,相信GC会很感谢你的。

5、List集合

不变性是很美好的,但是在大规模情境下,它就会有严重的缺陷。当传入一个List对象到方法中的情景。

当方法返回一个集合,通常会很明智的在方法中创建一个集合对象(如ArrayList),填充它,并以不变的集合的形式返回。

有些情况下,这并不会得到很好的效果。最明显的就是,当来自多个方法的集合调用一个final集合。因为不变性,在大规模数据情况下,会分配大量的临时集合。

这种情况的解决方案将不会返回新的集合,而是通过使用单独的集合当做参数传入到那些方法代替组合的集合。

例子1(低效率):

1
2
3
4
5
6
List<Item> items = new ArrayList<Item>();
for (FileData fileData : fileDatas)
{
// 每一次调用都会创建一个存储内部临时数组的临时的列表
items.addAll(readFileItem(fileData));
}

例子2:

1
2
3
4
5
6
7
List<Item> items =
new ArrayList<Item>(fileDatas.size() * avgFileDataSize * 1.5);
  
for (FileData fileData : fileDatas)
{
readFileItem(fileData, items); // 在内部添加记录
}

在例子2中,当违反不变性规则的时候(这通常应该被遵守),可以节省N个list的分配(以及任何临时数组的分配)。这将是对你GC的一个大大的优惠。

转载于:https://www.cnblogs.com/xianDan/p/4846820.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/419508.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML5DOM红蓝盒子,DOM介绍以及使用方法(示例代码)

DOM的基本讲解一、DOM(Document Object Model)文档对象模型1、有属性有方法1 var person {2 name:‘派大星‘,3 fav:function(){4 }5 }2、js中对象分类三种(1)用户定义对象(2)内建对象 Array Date Math (内置)(3)宿主对象3、Model Map(地图)(1)把 DOM 看做一颗“树”(2)DOM 把文…

JavaBean为什么要实现Serializable接口

Java"对象序列化"&#xff1a;是指将实现了Serializable接口的对象转换成一组byte&#xff0c;日后要用这个对象时候&#xff0c;可以根据byte数据恢复出来&#xff0c;并据此重新构建那个对象。 优点&#xff1a; 1、JavaBean类基本都要求实现了Serializable接口&…

html的post和get请求参数,HTTP 方法:GET 对比 POST | w3cschool菜鸟教程

HTTP 方法&#xff1a;GET 对比 POST两种最常用的 HTTP 方法是&#xff1a;GET 和 POST。什么是 HTTP &#xff1f;超文本传输协议(HTTP)的设计目的是保证客户端与服务器之间的通信。HTTP 的工作方式是客户端与服务器之间的请求-应答协议。web 浏览器可能是客户端&#xff0c;而…

scrollview 与 listView 的显示不全问题

使用两个GridView&#xff0c;两个GridView一起上下滚动&#xff1b;如果直接将两个GridView添加到同一个界面上&#xff0c;它们是各自滚动的。因此&#xff0c;我考虑使用SrollView&#xff0c;将它们包装一下&#xff01;但这样做会提示如下信息&#xff1a;The vertically …

Java编程中的基本概念

1.Java的JVM内部统一使用的字符表示是Unicode编码&#xff08;不选用任何特定的编码&#xff0c;直接使用它们在字符街中的编号&#xff0c;这是统一的唯一的方法&#xff09;。 2.在JVM加载类的时候&#xff0c;需要经过三个步骤&#xff1a;装载&#xff0c;连接&#xff0c…

怀化学院计算机科学专业排名,2019怀化学院专业排名

怀化学院创办于1958年&#xff0c;前身为怀化师范高等专科学校&#xff0c;2002年经国家教育部批准升格为全日制普通本科院校。不知道选择什么专业好的同学可以根据自己的学习成绩、兴趣爱好来选择自己喜欢的专业。下面是学习啦小编给大家带来的怀化学院专业排名&#xff0c;供…

C++中的也能使用正则表达式

正则表达式Regex(regular expression)是一种强大的描述字符序列的工具。在许多语言中都存在着正则表达式&#xff0c;C11中也将正则表达式纳入了新标准的一部分&#xff0c;不仅如此&#xff0c;它还支持了6种不同的正则表达式的语法&#xff0c;分别是&#xff1a;ECMASCRIPT、…

html文件设置ftp6,vsftp的安装与配置

环境mint17.21.安装dpkg -l|grep ftpsudo apt-get install vsftpd2.去根目录创建一个文件上传的文件夹sudo mkdir /ftpfile3.创建一个用户&#xff0c;他只对上传文件有权限&#xff0c;对系统登录无权限sudo useradd ftpuser -d /ftpfile/ -s /bin/bash4.用chown修改ftpfile的…

筛选法求1到100以内的素数

问题描述&#xff1a; 所谓“筛选法”指的是“埃拉托色尼(Eratosthenes)筛法”。他是古希腊的著名数学家。他采取的方法是&#xff0c;在一张纸上写上1到100全部整数&#xff0c;然后逐个判断它们是否是素数&#xff0c;找出一个非素数&#xff0c;就把它挖掉&#xff0c;最后剩…

Java基础知识强化之集合框架笔记27:ArrayList集合练习之去除ArrayList集合中的重复字符串元素...

1. 去除ArrayList集合中的重复字符串元素&#xff08;字符串内容相同&#xff09; 分析&#xff1a; &#xff08;1&#xff09;创建集合对象 &#xff08;2&#xff09;添加多个字符串元素&#xff08;包含重复的&#xff09; &#xff08;3&#xff09;创建新的集合 &#xf…

女生学医检好还是学计算机好,女生学医选择什么专业好?

就现在的医疗环境而言&#xff0c;学医不是最佳选择&#xff0c;很多医生都说不让自己的孩子再学医&#xff0c;这意味着什么&#xff0c;可想而知。但既然选择了学医&#xff0c;而且恰恰学医是自己的梦想的话&#xff0c;没有理由不去做好它。女孩子有自己的弱点&#xff0c;…

Java Servlet API中的forward()方法和redirect()方法的区别

forward&#xff08;&#xff09;&#xff1a;是容器中控制权的转让&#xff0c;在客户端浏览器地址栏不会显示转向后的地址。forward会将 request state、bean、等信息带到下一个jsp页面&#xff1b;使用getAttribute&#xff08;&#xff09;来取得前一个jsp所放的信息。默…

电子科技大学计算机读博好毕业,高产博士生读博一年达毕业要求:写论文不无聊...

(原标题&#xff1a;成电学子读博一年达毕业要求&#xff1a;写论文不无聊&#xff0c;别把挂科当潮流)扎在实验室写代码、跑数据的许潇突然成为全校谈论的焦点&#xff0c;一连串“牛”、“太强了”、“佩服”钻进耳朵。“21岁保研到成电(电子科技大学)&#xff0c;22岁成为国…

数学入门题——《算法竞赛入门经典-训练指南》

题目链接&#xff1a;http://acm.hust.edu.cn/vjudge/contest/view.action?cid94017#overview 代码链接&#xff1a;https://github.com/YvetteYue/ACM/tree/master/math%E5%85%A5%E9%97%A8 A题&#xff1a;UVA11388 GCD LCM 这道题求得是已知GCD和LCM 求最小的a情况下的a和b …