减少Java垃圾收集开销的5条提示

保持较低的GC开销的一些最有用的技巧是什么?

随着Java 9的一次再次延迟发布,G1(“ Garbage First”)垃圾收集器将设置为HotSpot JVM的默认收集器。 从串行垃圾收集器一直到CMS收集器,JVM在其整个生命周期中都见证了许多GC实现,而G1收集器紧随其后。

随着垃圾收集器的发展,每一代(没有双关语)都会带来比以前更高的进步和改进。 串行收集器之后的并行GC利用多核计算机的计算功能使垃圾收集成为多线程。 随后的CMS(“并发标记扫描”)收集器将收集分为多个阶段,从而使许多收集工作可以在应用程序线程运行时同时完成-从而减少了“停止世界”的频率。 G1在堆非常大的JVM上增加了更好的性能,并且具有更加可预测的统一暂停。

无论高级GC收到什么,其致命弱点仍然是:冗余且不可预测的对象分配。 无论您选择使用哪种垃圾收集器,这些快速,适用,永恒的技巧将帮助您避免GC开销。

提示1:预测收集容量

所有标准Java集合以及大多数自定义和扩展的实现(例如Trove和Google的Guava )都使用基础数组(基于原始或对象的数组)。 由于数组一旦分配就不会改变大小,因此在许多情况下向集合中添加项目可能会导致丢弃旧的基础数组,而使用较大的新分配的数组。

即使未提供预期的集合大小,大多数集合实现都尝试优化此重新分配过程并将其保持在摊销后的最小值。 但是,通过在构造时为集合提供预期的大小可以达到最佳效果。

让我们以以下代码为简单示例:

public static List reverse(List<? extends T> list) {List result = new ArrayList();for (int i = list.size() - 1; i >= 0; i--) {result.add(list.get(i));}return result;
}

此方法分配一个新数组,然后以相反的顺序填充另一个列表中的项目。

可能会很痛苦并且可以优化的一点是将项目添加到新列表的行。 对于每个添加项,列表都需要确保其基础数组中具有足够的可用插槽以容纳新项。 如果是这样,它将简单地将新项目存储在下一个空闲插槽中。 如果不是,它将分配一个新的基础数组,将旧数组的内容复制到新数组中,然后添加新项。 这将导致阵列的多个分配,这些分配将保留在那里,以供GC最终收集。

我们可以通过在构造数组时让数组知道预计要保留多少个项来避免这些多余的分配:

public static List reverse(List<? extends T> list) {List result = new ArrayList(list.size());for (int i = list.size() - 1; i >= 0; i--) {result.add(list.get(i));}return result;}

这使得ArrayList构造函数执行的初始分配足够大,可以容纳list.size()项,这意味着在迭代期间不必重新分配内存。

Guava的集合类更进一步,使我们可以使用预期项目的确切数量或估计值来初始化集合。

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

前者用于以下情况:我们确切知道集合将要容纳多少项,而后者则分配一些填充以解决估计误差。

提示2:直接处理流

例如,在处理数据流(例如从文件读取的数据或通过网络下载的数据)时,通常会看到以下内容:

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

然后,可以将结果字节数组解析为XML文档,JSON对象或协议缓冲区消息,以列举一些常用的选项。

当处理大文件或大小无法预测的文件时,这显然不是一个好主意,因为在JVM无法实际分配整个文件大小的缓冲区的情况下,这会使我们面临OutOfMemoryErrors。

但是,即使数据的大小似乎是可管理的,使用上述模式在进行垃圾回收时也会导致大量开销,因为它会在堆上分配一个较大的blob来保存文件数据。

解决此问题的更好方法是使用适当的InputStream(在这种情况下为FileInputStream),将其直接输入解析器,而无需先将整个内容读取到字节数组中。 所有主要库都公开了API以直接解析流,例如:

FileInputStream fis = new FileInputStream(fileName);
MyProtoBufMessage msg = MyProtoBufMessage.parseFrom(fis);

提示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暂停时间越短,总体吞吐量就越高。

提示4:警惕字符串连接

在任何基于JVM的应用程序中,字符串可能是最普遍的非原始数据结构。 但是,它们隐含的重量和易于使用的特性使它们很容易成为导致应用程序占用大量内存的罪魁祸首。

问题显然不在于文字字符串,因为它们是内联和插入的,而是在于在运行时分配和构造的字符串。 让我们看一下动态字符串构造的快速示例:

public static String toString(T[] array) {String result = "[";for (int i = 0; i < array.length; i++) {result += (array[i] == array ? "this" : array[i]);if (i < array.length - 1) {result += ", ";}}result += "]";return result;
}

这是一个不错的方法,它接受一个数组并为其返回字符串表示形式。 在对象分配方面也是如此。

很难理解所有这些语法糖,但是幕后的实际情况是:

public static String toString(T[] array) {String result = "[";for (int i = 0; i < array.length; i++) {StringBuilder sb1 = new StringBuilder(result);sb1.append(array[i] == array ? "this" : array[i]);result = sb1.toString();if (i < 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类来实际执行这些串联。 这导致了双重麻烦,因为在循环的每次迭代中,我们同时获得(1)临时字符串的隐式分配和(2)临时StringBuilder对象的隐式分配,以帮助我们构造最终结果。

避免这种情况的最佳方法是显式使用StringBuilder并将其直接附加到其上,而不是使用有些天真的串联运算符(“ +”)。 可能是这样的:

public static String toString(T[] array) {StringBuilder sb = new StringBuilder("[");for (int i = 0; i < array.length; i++) {sb.append(array[i] == array ? "this" : array[i]);if (i < array.length - 1) {sb.append(", ");}}sb.append("]");return sb.toString();
}

在此方法的开头,我们仅分配了一个StringBuilder。 从那时起,所有字符串和列表项都附加到唯一的StringBuilder上,该字符串最终仅使用其toString方法转换成字符串,然后返回。

提示5:使用专门的原始集合

Java的标准集合库既方便又通用,允许我们使用具有半静态类型绑定的集合。 如果我们想使用例如一组字符串(Set <String>),或一对和一组字符串之间的映射(Map <Pair,List <String >>),这是很棒的。

真正的问题始于我们要保存一个int列表或一个double类型值的映射。 由于泛型类型不能与基元一起使用,因此替代方法是使用装箱的类型,因此我们需要使用List <Integer>来代替List <int>。

这是非常浪费的,因为Integer是一个完整的Object,充斥着12字节的对象标头和一个内部4字节的int字段来保存其值。 每个Integer项的总和为16个字节。 这是相同大小的原始整数列表的大小的4倍! 但是,更大的问题是所有这些Integer实际上都是对象实例,在垃圾回收期间需要考虑这些实例。

为了解决这个问题,我们在塔基皮(Takipi)使用了出色的Trove收藏库。 Trove放弃了一些(但不是全部)泛型,转而使用专门的内存有效的原始集合。 例如,代替浪费的Map <Integer,Double>,还有TIntDoubleMap形式的专门替代方法:

TIntDoubleMap map = new TIntDoubleHashMap();
map.put(5, 7.0);
map.put(-1, 9.999);
...

Trove的基础实现使用基本数组,因此在操作集合时不会进行装箱(int-> Integer)或拆箱(Integer-> int),并且不会存储任何对象来代替基元。

最后的想法

随着垃圾收集器的不断发展,以及运行时优化和JIT编译器变得越来越智能,我们作为开发人员将发现自己越来越不关心如何编写与GC友好的代码。 但是,就目前而言,无论G1多么先进,我们仍然可以做很多工作来帮助JVM。

翻译自: https://www.javacodegeeks.com/2015/12/5-tips-reducing-java-garbage-collection-overhead.html

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

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

相关文章

关于collectionview布局的坑

不知道写了多少次collectionview&#xff0c;步了很多坑&#xff0c;现在看来虽然达到了自己想要的结果&#xff0c;却不知道其中所以然。还是总结一下&#xff0c;免得再走弯路&#xff1b; 场景是这样的&#xff0c;我要定制一个显示选择图片的排列&#xff0c;想要实现横向排…

python可以用来编写计算机网络程序吗_计算机网络(基于python做的笔记 )

计算机网络(UDP 和 TCP)概述为了让在不同的电脑上运行的软件&#xff0c;之间能够互相传递数据&#xff0c;就需要借助网络的功能使用网络能够把多方链接在一起&#xff0c;然后可以进行数据传递所谓的网络编程就是&#xff0c;让在不同的电脑上的软件能够进行数据传递&#xf…

git冲突Please move or remove them before you can merge

解决Git冲突造成的Please move or remove them before you can merge git clean -d -fx ""其中x -----删除忽略文件已经对git来说不识别的文件d -----删除未被添加到git的路径中的文件f -----强制运行 转载于:https://www.cnblogs.com/wicub/p/6934597.html

linux不重启换root密码是什么原因,在Linux下修改和重置root密码的方法(超简单)

刚开始接触linux的人&#xff0c;忘记了root密码可能会不知所措。想找回自己的root密码&#xff0c;但是又不知道方法。其实&#xff0c;只需要简单的几步就可以重置自己的root密码了(找回密码我也不会)1.开机HcQBEm上敲击e&#xff0c;然后编辑选项2.在linux16这一行&#xff…

python命令行参数作用_Python命令行参数解析模块argparse

前言更多内容&#xff0c;请访问我的 个人博客。以前 optparse 比较火&#xff0c;但是在python2.7版本后&#xff0c;官方不再维护这个模块&#xff0c;转而大力支持 argparse 。argparse 模块可以让人轻松编写用户友好的命令行接口。她可以从 sys.argv 中解析出参数&#xff…

SCP-bzoj-1019

项目编号&#xff1a;bzoj-1019 项目等级&#xff1a;Safe 项目描述&#xff1a; 戳这里 特殊收容措施&#xff1a; 对于一个hanoi&#xff0c;知道了各种移动操作的优先级&#xff0c;也就确定了方案。可以证明对于盘子数为N的hanoi&#xff0c;任意移动方案都等价于将数目为N…

shell编程入门 linux解释器原理,Shell编程入门Linux解释器原理详细介绍 使用Shell进行工作的人们对.doc...

Shell编程入门Linux解释器原理详细介绍 使用Shell进行工作的人们对Shell编程入门&#xff1a;Linux解释器原理详细介绍使用Shell进行工作的人们对Unix/Linux下的Shell编程都很熟悉&#xff0c;在所有的Shell编程的书中都会提到#!/bin/bash,而这里到底包含了些什么&#xff1f;对…

一键分享手机代码_通过广告路由器指定手机浏览器自动认证WIFI上网 附代码

说说应用过程&#xff0c;下面用手机QQ浏览器为例。在路由器搭建免费WIFI&#xff0c;用户连接免费WIFI后&#xff0c;使用手机QQ浏览器点击打开任意网页即可自动通过认证并上网&#xff0c;有的手机会自动打开认证网页&#xff0c;如果使用其他手机浏览器则自动跳转到引导认证…

netbeans7.4_NetBeans 7.1:创建自定义提示

netbeans7.4我已经在帖子中对我最喜欢的NetBeans提示进行了讨论&#xff0c;这些帖子中包含用于现代化Java代码的七个NetBeans提示和七个不可或缺的NetBeans Java提示 。 这两个帖子中涉及的十四个提示仅占NetBeans支持的“即开即用”提示总数的一小部分。 但是&#xff0c;由于…

linux uboot启动流程分析,uboot启动流程分析

uboot版本为NXP维护的2016.03版本下载地址为http://git.freescale.com/git/...分析uboot的启动流程&#xff0c;需要编译一下uboot&#xff0c;然后打开链接脚本u-boot.lds在u-boot.lds中1 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf…

matlab求解线性方程组

模电题现在看来是不用matlab解方程不可做了orz 绝望&#xff0c;各种绝望&#xff0c;平时不努力到了期末季就焦虑得不行。 左除法就好 xA/b; 如果有符号变量&#xff0c;用syms声明一下就好。 越来越懒了orz好吧&#xff0c;解线性方程组这种毫无技术含量的东西用matlab倒是没…

魔术命令python_Python前10个魔术命令可以帮助您提高生产率

注意&#xff1a;Python不仅是最常用的编程语言&#xff0c;而且在集成新函数时也是最灵活的。例如&#xff0c;magic命令是Python shell的重要功能之一。让我们来看看10个简单的魔法命令来帮助工程师提高生产率。Python中的magic命令是什么&#xff1f;Magic命令是对常规Pytho…

JavaOne 2015 –第二十版十大收获

我们刚刚在旧金山有了JavaOne的第二十版。 这将是我自2004年以来第十二次参加不间断的系列活动。最大的教训是什么&#xff0c;可以揭示Java的未来。 模块化斗争 自从Java 2007首次提到模块以来&#xff0c;已经花费了将近9年的时间&#xff0c;或者说&#xff0c;直到2016年9…

linux can接口不使用过滤,linux can 总线socket接口测试使用

最近调试一个sja1000的can驱动&#xff0c;发现到了2.6.36&#xff0c;linux把can总线封装成了网络接口。内核文档里给出了这么修改的原因。1. Overview / What is Socket CAN--------------------------------The socketcan package is an implementation of CAN protocols (C…

IOS--文件管理NSFileManager

iOS的沙盒机制。应用仅仅能訪问自己应用文件夹下的文件。iOS不像android。没有SD 卡概念。不能直接訪问图像、视频等内容。iOS应用产生的内容&#xff0c;如图像、文件、缓存内容等都必须存储在自己的沙盒内。默认情况下&#xff0c;每一个沙盒含有3个文件 夹&#xff1a;Docum…

linux修改文件内容_详解5种实用方法---Linux系统清空或删除大文件内容

概述有时我们在处理Linux终端中的文件时&#xff0c;可能要去清除文件的内容&#xff0c;而无需使用任何Linux命令行编辑器打开它。怎么才能实现呢&#xff1f;下面通过几种不同的方式教大家清空文件内容。1.通过重定向到空来清空文件内容使用shell重定向null(不存在的对象)清空…

c语言include不起作用,c语言中include的使用方法

c语言中include的使用方法发布时间&#xff1a;2020-06-16 09:09:37来源&#xff1a;亿速云阅读&#xff1a;185作者&#xff1a;Leah这篇文章将为大家详细讲解有关c语言中include的使用方法&#xff0c;小编觉得挺实用的&#xff0c;因此分享给大家做个参考&#xff0c;希望大…

linux下如何安装配置redis及主从配置

redis是一种非关系型数据存储工具&#xff0c;这区别于传统的关系型数据库(像MySQL等)&#xff0c;类似于memcache&#xff0c;并且其内部集成了对list(链表)、set(集合)的操作&#xff0c;可以很方便快速的处理数据(像插入、删除list取交集 并集 差集等)&#xff0c;这极大的减…

jsf集成spring_Spring和JSF集成:动态导航

jsf集成spring通常&#xff0c;您的JSF应用程序将需要超越基本的静态导航并开始做出动态导航决策。 例如&#xff0c;您可能想根据用户的年龄重定向他们。 大多数JSF教程建议通过将命令的action属性绑定到支持bean来实现动态导航&#xff1a; <h:commandButton action"…

python 3.6 tensorflow_无法在python 3.6中导入Tensorflow

我无法导入Tensorflow。 我的GPU nvidia 940mx和我正在使用python 3.6。我安装的软件包是&#xff1a;absl-py(0.2.0)阿斯特(0.6.2)漂白剂(1.5.0)循环器(0.10.0)气(0.2.0)grpcio(1.11.0)html5lib(0.9999999)猕猴桃(1.0.1)降价(2.6.11)matplotlib(2.2.2)numpy的(1.14.2)opencv-p…