口碑好网站建设电话,网站更改建设方案模板,安卓app开发软件有哪些,襄阳作风建设年网站java timer开销保持较低的GC开销的一些最有用的技巧是什么#xff1f; 随着Java 9即将再次延迟发布#xff0c;G1#xff08;“垃圾优先”#xff09;垃圾收集器将设置为HotSpot JVM的默认收集器。 从串行垃圾收集器一直到CMS收集器#xff0c;JVM在其整个生命周期中都见… java timer开销 保持较低的GC开销的一些最有用的技巧是什么 随着Java 9即将再次延迟发布G1“垃圾优先”垃圾收集器将设置为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对象或Protocol Buffer消息以列举一些常用的选项。 当处理大文件或大小无法预测的文件时这显然不是一个好主意因为在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 PairList String 则这是很棒的。 真正的问题始于我们要保存一个int列表或一个double类型值的映射。 由于泛型类型不能与基元一起使用因此替代方法是使用装箱的类型因此我们需要使用List Integer来代替List int。 这是非常浪费的因为Integer是一个完整的Object充斥着12字节的对象标头和一个内部4字节的int字段来保存其值。 每个Integer项的总和为16个字节。 这是相同大小的原始整数列表的大小的4倍 但是更大的问题是所有这些Integer实际上都是对象实例在垃圾回收期间需要考虑这些实例。 为了解决这个问题我们在塔基皮Takipi使用了出色的Trove收藏库。 Trove放弃了一些但不是全部泛型转而使用专门的内存有效的原始集合。 例如不是浪费的Map IntegerDouble而是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.htmljava timer开销