降低Java垃圾回收开销的5条建议

news/2024/4/19 16:20:44/文章来源:https://www.cnblogs.com/ebuybay/p/16638566.html

  保持GC低开销的窍门有哪些?

  随着一再拖延而即将发布的 Java9,G1(“Garbage First”)垃圾回收器将被成为 HotSpot 虚拟机默认的垃圾回收器。从 serial 垃圾回收器到CMS 收集器, JVM 见证了许多 GC 实现,而 G1 将成为其下一代垃圾回收器。

  随着垃圾收集器的发展,每一代 GC 与其上一代相比,都带来了巨大的进步和改善。parallel GC 与 serial GC 相比,它让垃圾收集器以多线程的方式工作,充分利用了多核计算机的计算能力。CMS(“Concurrent Mark-Sweep”)收集器与 parallel GC 相比,它将回收过程分成了多个阶段,使得应用线程正在运行的时候,收集工作可以并发地完成,大大改善了频繁执行 “stop-the-world” 的情况。G1 对于拥有大量堆内存的 JVM 表现出更好的性能,并且具有更好的可预测和统一的暂停过程。

  Tip #1: 预测集合的容量

  所有标准的 Java 集合,包括定制和扩展的实现(比如 Trove 和 Google 的 Guava),底层都使用了数组(原生数据类型或者基于对象的类型)。因为数组一旦被分配,其大小就不可变,因此添加元素到集合时,大多数情况下都会导致需要重新申请一个新的大容量数组替换老的数组(指集合底层实现使用的数组)。

  即使没有提供集合初始化的大小,大多数集合的实现都尽量优化重新分配数组的处理并且将其开销平摊到最低。不过,在构造集合的时候就提供大小可以得到最佳的效果。

  让我们将下面的代码作为一个简单的例子分析一下:

  public static List reverse(List & lt; ? extends T & gt; list) {

  List result=new ArrayList();

  for (int i=list.size() - 1; i & gt;=0; i--) {

  result.add(list.get(i));

  }

  return result;

  }

  This method allocates a new array, then fills it up with items from another list, only in reverse order. 这个方法分配了一个新的数组,然后用另一个 list 中元素对该数组进行填充,只是元素的数序发生了变化。

  这个处理方式可能会付出惨重的性能代价,其优化的点在添加元素到新的 list 中这行代码。 随着每一次添加元素,list 都需要确保其底层数组拥有足够的位置来容纳新的元素。如果有空闲的位置,那么只是简单地将新元素存储到下一个空闲的槽位。如果没有的话,将分配一个新的底层数组,拷贝旧的数组内容到新的数组中,然后添加新的元素。这将导致多次分配数组,那些剩余的旧数组最终被 GC 所回收。

  我们可以通过在构造集合时让其底层的数组知道它将存储多少元素,从而避免这些多余的分配

  public static List reverse(List & lt; ? extends T & gt; list) {

  List result=new ArrayList(list.size());

  for (int i=list.size() - 1; i & gt;=0; i--) {

  result.add(list.get(i));

  }

  return result;

  }

  上面的代码通过 ArrayList 的构造器指定足够大的空间来存储 list.size() 个元素,在初始化时完成分配的执行,这意味着 List 在迭代的过程中无需再次分配内存。

  Guava 的集合类则更进一步,允许初始化集合时明确指定期望元素的个数或者指定一个预测值。

  List result=Lists.newArrayListWithCapacity(list.size());

  List result=Lists.newArrayListWithExpectedSize(list.size());

  上面的代码中,前者用于我们已经准确地知道集合将要存储多少元素,而后者的分配方式考虑了错误预估的情况。

  Tip #2:直接处理数据流

  当处理数据流时,比如从一个文件读取数据或者从网络中下载数据,下面的代码是非常常见的:

  1byte[] fileData=readFileToByteArray(new File("myfile.txt"));

  所产生的字节数组可能被解析 XML 文档、JSON 对象或者协议缓冲消息,以及一些常见的可选项。

  当处理大文件或者文件的大小无法预测时,上面的做法很是不明智的,因为当 JVM 无法分配一个缓冲区来处理真正文件时,就会导致OutOfMemeoryErrors。

  即使数据的大小是可管理的,当到垃圾回收时,使用上面的模式依然会造成巨大的开销,因为它在堆中分配了一块非常大的区域来存储文件数据。

  一种更加好的处理方式是使用合适的 InputStream (比如在这个例子中使用 FileInputStream)直接传递给解析器,不再一次性将整个文件读取到一个字节数组中。所有主流的开源库都提供相应的 API 来直接接受一个输入流进行处理,比如:

  FileInputStream fis=new FileInputStream(fileName);

  MyProtoBufMessage msg=MyProtoBufMessage.parseFrom(fis);

  Tip #3: 使用不可变的对象

  不变性有太多的好处。甚至不用我赘述什么。然而,有一个优点会对垃圾回收产生影响,应该关注一下。

  一个不可变对象的属性在对象被创建后就不能被修改(在这里的例子使用的是引用数据类型的属性),比如:

  public class ObjectPair {

  private final Object first;

  private final Object second;

  public ObjectPair(Object first, Object second) {

  this.first=first;

  this.second=second;

  }

  public Object getFirst() {

  return first;

  }

  public Object getSecond() {

  return second;

  }

  }

  将上面的类实例化后会产生一个不可变对象—它的所有属性用 final 修饰,构造完成后就不能改变了。

  不可变性意味着所有被一个不可变容器所引用的对象,在容器构造完成前对象就已经被创建。就 GC 而言:这个容器年轻程度至少和其所持有的最年轻的引用一样。这意味着当在年轻代执行垃圾回收的过程中,GC 因为不可变对象处于老年代而跳过它们,直到确定这些不可变对象在老年代中不被任何对象所引用时,才完成对它们的回收。

  更少的扫描对象意味着对内存页更少的扫描,越少的扫描内存页就意味着更短的 GC 生命周期,也意味着更短的 GC 暂停和更好的总吞吐量。

  Tip #4: 小心字符串拼接

  字符串可能是在所有基于 JVM 应用程序中最常用的非原生数据结构。然而,由于其隐式地开销负担和简便的使用,非常容易成为占用大量内存的罪归祸首。

  这个问题很明显不在于字符串字面值,而是在运行时分配内存初始化产生的。让我们快速看一下动态构建字符串的例子:

  public static String toString(T[] array) {

  String result="[";

  for (int i=0; i & lt; array.length; i++) {

  result +=(array[i]==array ? "this" : array[i]);

  if (i & lt; array.length - 1) {

  result +=", ";

  }

  }

  result +="]";

  return result;

  }

  这是个看似不错的方法,接收一个字符数组然后返回一个字符串。但是这对于对象内存分配却是灾难性的。

  很难看清这语法糖的背后,但是幕后的实际情况是这样的:

  public static String toString(T[] array) {

  String result="[";

  for (int i=0; i & lt; array.length; i++) {

  StringBuilder sb1=new StringBuilder(result);

  sb1.append(array[i]==array ? "this" : array[i]);

  result=sb1.toString();

  if (i & lt; array.length - 1) {

  StringBuilder sb2=new StringBuilder(result);

  sb2.append(", ");

  result=sb2.toString();

  }

  }

  StringBuilder sb3=new StringBuilder(result);

  sb3.append("]");

  result=sb3.toString();

  return result;

  }

  字符串是不可变的,这意味着每发生一次拼接时,它们本身不会被修改,而是依次分配新的字符串。此外,编译器使用了标准的 StringBuilder 类来执行这些拼接操作。这就会有问题了,因为每一次迭代,既隐式地分配了一个临时字符串,又隐式分配了一个临时的 StringBuilder 对象来帮助构建最终的结果。

  最佳的方式是避免上面的情况,使用 StringBuilder 和直接的追加,以取代本地拼接操作符(“+”)。下面是一个例子:

  public static String toString(T[] array) {

  StringBuilder sb=new StringBuilder("[");

  for (int i=0; i & lt; array.length; i++) {

  sb.append(array[i]==array ? "this" : array[i]);

  if (i & lt; array.length - 1) {

  sb.append(", ");

  }

  }

  sb.append("]");

  return sb.toString();

  }

  这里,我们只在方法开始的时候分配了唯一的一个 StringBuilder。至此,所有的字符串和 list 中的元素都被追加到单独的一个StringBuilder中。最终使用 toString() 方法一次性将其转成成字符串返回。

  Tip #5: 使用特定的原生类型的集合

  Java 标准的集合库简单且支持泛型,允许在使用集合时对类型进行半静态地绑定。比如想要创建一个只存放字符串的 Set 或者存储 Map<Pair, List>这样的 map,这种处理方式是非常棒的。

  真正的问题源于当我们想要使用一个 list 存储 int 类型,或者一个 map 存储 double 类型作为 value。因为泛型不支持原生数据类型,因此另外的一种选择是使用包装类型来进行替换,这里我们使用 List 。

  这种处理方式是非常浪费的,因为一个 Integer 是一个完全的对象,一个对象的头部占用12个字节以及其内部的所维护的 int 属性,每个Integer 对象总共占用16个字节。这比起存储相同个数的 int 类型的 list 而言,其消耗的空间是它的四倍!比这个更加严重的问题在于,事实上因为 Integer 是真正的对象实例,因此它需要垃圾收集阶段被垃圾收集器所考虑是否要回收。

  为了处理这个问题,我们在 Takipi 中使用非常棒的 Trove 集合库。Trove 摒弃了部分泛型的特定来支持特定的使用内存更高效的原生类型的集合。比如,我们使用非常消耗性能的 Map<Integer, Double>,在 Trove 中有另一种特别的选择方案,其形式为 TIntDoubleMap

  TIntDoubleMap map=new TIntDoubleHashMap();

  map.put(5, 7.0);

  map.put(-1, 9.999);

  ...

  Trove 的底层实现使用了原生类型的数组,所以当操作集合的时候不会发生元素的装箱(int->Integer)或者拆箱(Integer->int), 没有存储对象,因为底层使用原生数据类型存储。

  最后

  随着垃圾收集器持续的改进,以及运行时的优化和 JIT 编译器也变得越来越智能。我们作为开发者将会发现越来越少地考虑如何编写 GC 友好的代码。然而,就目前阶段,不论 G1 如何改进,我们仍然有很多可以做的事来帮 JVM 提升性能。

  码字不易看到最后了,那就点个关注呗,只收藏不点关注的都是在耍流氓!

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

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

相关文章

科普达人丨一图看懂安全组

建议收藏安全组是一种虚拟防火墙,通过安全组规则控制 ECS 实例出/入方向的流量,保障云服务器的安全。本文将通过介绍安全组的工作原理、功能、默认安全组和规则,以及快速上手使用安全组的操作等方面的介绍,您对于安全组有一个全面的了解,帮助您更好、更安全地开展业务上云…

京东云PostgreSQL在GIS场景的应用分享

在地图或地理信息有关的场景里,地址关键词的检索尤其重要。比如打开百度地图,想要查询某个位置的信息“北京市海淀区清华东路17号中国农业大学”,往往我们输入的是关键词“中国农业大学”而不是精确到街道的详细地址信息。在地图或地理信息有关的场景里,地址关键词的检索尤…

超全的正则表达式速查手册

一、校验数字的表达式 数字:^[0-9]*$ n位的数字:^\d{n}$ 至少n位的数字:^\d{n,}$ m-n位的数字:^\d{m,n}$ 零和非零开头的数字:^(0|[1-9][0-9]*)$ 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$ 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$ 正…

HCIA学习笔记二十六:手工负载分担模式二层链路聚合

一、链路聚合的应用场景• 链路聚合一般部署在核心结点,以便提升整个网络的数据吞吐量。 二、链路聚合• 链路聚合能够提高链路带宽,增强网络可用性,支持负载分担。 三、链路聚合模式• 手工负载分担模式下所有活动接口都参与数据的转发,分担负载流量。 • LACP模式支持链路…

Kotlin的空检查

我们在使用Java语言时,经常会出现空指针异常NullPointerException。Kotlin基于过往语言设计的经验对这一问题进行了改良,把运行时可能出现的null问题,以编译时错误的方式,提前在编译期强迫我们重视起来,而不是等到运行时报错,防患于未然,提高我们程序的健壮性。 Kotlin语…

智慧城市建设的三个阶段

今天的中国城市,正在疾步向前拥抱智慧时代,我国是全球智慧城市建设最为积极的国家之一。近年来,随着政策红利进一步释放与资金的大量投入,智慧城市产业也将迎来新的发展高潮。智慧城市建设步入快车道时代!据不完全统计,中国智慧城市的发展数量已经超过500个,居世界之最。…

2021年 西南石油大学超算与并行计算团队南充校区分队 第二届招新赛题解

2021年SWPU(南充)超算团队招新赛总体难度并不是很大,大部分题目考察的是基本的编程能力,题目中涉及到了一些并行计算相关的名词和知识,选手在参加比赛的同时,既能够展示自己的实力,也可以学习到相关的一些知识。下面是本次招新赛的题目A.简单输出 题目描述:题目要求:输出…

Java并发编程总结

——《Java多线程编程实战指南》学习及其他参考博客总结 串行、并行、并发 (1)串行:顺序执行多个任务,一个时刻只有一个任务在执行 (2)并行:多个CPU(核)同一时间多个任务,一个时刻有多个任务在执行 (3)并发:单个CPU(核)同一时间间隔内交替执行多个任务,一个时刻只有一…

学习随笔——洛谷题目P1636解答

摘要:欧拉图的应用。 题目原地址如下:https://www.luogu.com.cn/problem/P1636 题目截图如下: 一笔画问题,考察欧拉回路的定义,即所有节点的入度出度的和都为偶数即可满足欧拉回路的性质。我们为方便分析可加入一条线,发现加入一条边后会改变两个点的度数和,只需寻找奇数…

Spring的自动化装配

在Spring中,对象无需自己查找和创建与其所关联的其他对象。相反,容易负责把需要相互协作的对象引用赋予各个对象。例如,一个订单管理的组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组…

jQuery使用ajax

1.导入jQuery的js库2.jQuery发送单一的get请求$.get(url:接口地址,data:{id:1,name:2,......}function(res){// res是服务器返回的数据} ) 3.jQuery发送单一的post请求$.post(url:接口地址,data:{id:1,name:哈哈哈,......}function(res){// res是服务器返回的数据} ) 4.jQuery发…

服务器TIME_WAIT和CLOSE_WAIT详解和解决办法

服务器TIME_WAIT和CLOSE_WAIT详解和解决办法 - 悟寰轩-叶秋 - 博客园 https://www.cnblogs.com/sunxucool/p/3449068.html 昨天解决了一个HttpClient调用错误导致的服务器异常,具体过程如下: http://blog.csdn.net/shootyou/article/details/6615051 里头的分析过程有提到,…

引入VUE的方式(8种)

第一类: 1、本地引入 把vue的js文件下载下来引入 2、CDN引入 把vue.js网址引入 3、把vue.js文件放在项目文件夹src中引入项目 然后webpack打包4、编辑器直接生成cdn的方式第二类: 5、自己构建vue的脚手架/* 1.新建项目 alipay 2.初始化配置文件:npm init -y 3.下载依赖:npm…

PipeCAD-捕捉选项

PipeCAD-捕捉选项PipeCAD-捕捉选项 eryar@163.com Key Words. PipeCAD, 三维管道设计软件,三维工厂设计软件,三维配管软件 1 概述 在PipeCAD交互设计过程中,有些建模操作需要在模型中捕捉点来进行定位。通过捕捉点可以快速、准确建模。一般的CAD软件中都有捕捉功能,为了给用…

CentOS 安装Nginx并部署vue项目

安装 yum install nginx配置nginx设置开机启动 systemctl enable nginx启动服务 systemctl start nginx停止服务 systemctl stop nginx重启服务 systemctl restart nginx修改配置后热重载 systemctl reload nginxnginx常用目录路径 説明/etc/nginx/ 保存Nginx设置文件的目录/et…

多示例学习

在机器学习中,多示例学习(Multiple Instance Learning 简称 MIL)是由监督型学习算法演变出的一种方法,定义“包”为多个示例的集合,具有广泛的应用。学习者不是接收一组单独标记的实例,而是接收一组带标签的包,每个包拥有多个实例。在多实例二进制分类的简单情况下,如果包…

在一些常见用例中修复详尽的deps警告

反应开发 在一些常见用例中修复详尽的deps警告在里面 上一篇文章 ,我们查看了正确使用 useEffect 钩子需要采用的正确心智模型。在本文中,让我们看看如何调整这种思维模型来解决一些常见的用例。这也将帮助您避免详尽的部门警告。 在 mount 上做某事 首先,我认为这个想法本身…

js实现幻灯片

使用原生js实现轮播图 html代码<div class="slide"><ul><li style="display: block;"><img src="1.jpg"></li><li><img src="2.jpg"></li><li><img src="3.jpg"&…

字节跳动基于 ClickHouse 优化实践之“查询优化器”

更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 相信大家都对大名鼎鼎的 ClickHouse 有一定的了解了,它强大的数据分析性能让人印象深刻。但在字节大量生产使用中,发现了 ClickHouse 依然存在了一定的限制。例如:缺少完整的 upsert…

echarts-dataset数据源配置项

如下效果图: 代码入下:let box4 = document.querySelector(.box4)let myCharts3 = echarts.init(box4)myCharts3.setOption({dataset:{// 二维数组存放数据source:[// 0 1 2 3 4 5 六个维度[衣服,22,15,36,35,18],[食品,60,39,50,15,22],[生活用品,60,52,36,15…