总览
有两个很好的理由在可能的地方使用原语而不是包装器。
- 明晰。 通过使用原语,您可以清楚地知道null值是不合适的。
- 性能。 使用原语通常更快。
清晰度通常比性能更重要,并且是使用它们的最佳理由。 但是,本文讨论了使用包装程序对性能的影响。
我对本文如何避免垃圾回收非常感兴趣,但是缺少很多实际细节。 这是减少GC需求的系列文章中的第一篇。
使用包装器的性能
以下微基准的行为与许多应用程序相同。
使用包装器和包装器集合循环
Map<Integer, Integer> counters = new HashMap<Integer, Integer>();
int runs = 20 * 1000;
for (Integer i = 0; i < runs; i++) {Integer x = i % 12;Integer y = i / 12 % 12;Integer times = x * y;Integer count = counters.get(times);if (count == null)counters.put(times, 1);elsecounters.put(times, count + 1);
}
这将为每个任务创建对象。 虽然将int用作循环计数器是一种常见的做法,但是使用Iterator也是一种常见的做法。您可以使用此微基准的类型和参数,但是您会获得许多尝试过的开发人员熟悉的内存配置文件调整他们的应用程序。 使用VisualVM,在五分钟的时间内,堆使用情况看起来像这样。
大约6分钟内有20个次要GC。
每个循环的平均时间为亚微秒,非常快。
每个循环消耗4,099 ns
每个回路占用559 ns
每个循环消耗115 ns 每个回路占用240 ns 每个回路占用255 ns 在第一个测试中,JVM尚未预热。
使用原语真的可以带来很大的不同吗?
使用原语的性能
以下基准测试与大多数应用程序的行为截然不同。 即使它所做的工作与先前的基准测试相同,也不会创建任何对象。
使用基元和数组循环
int[] counters = new int[144];
int runs = 20 * 1000;
for (int i = 0; i < runs; i++) {int x = i % 12;int y = i / 12 % 12;int times = x * y;counters[times]++;
}
堆的使用情况反映了这一点
5分钟内没有GC。 该测试可能运行了更长的时间,但仍未触发GC。
而且每个循环的平均时间也要短得多
每个循环198 ns
每个循环占用17 ns
每个循环占用16 ns 每个循环占用14 ns 每个循环占用15 ns
在第一个测试中,JVM尚未预热。
结论
使用基元会更好。 (除非有过多的装箱和拆箱)
即使在性能不是很关键的应用程序中,它也可以提高代码的清晰度,并且当您尝试对应用程序进行概要分析时,它也可以减少“噪声”级别,从而使问题更清晰。
笔记
即使在很少创建对象的测试中,您也可以看到一些对象分配。 这主要是由于VisualVM的轮询。 为了减少这种情况,我将轮询间隔从3秒更改为20秒。
使用-XX:NewSize = 100m增加Eden大小以使图形更清晰(不建议使用此值(也许除了微基准测试)),但是它是您可能需要针对应用程序进行调整的参数。
完整代码
- 原始基准
- 包装基准
参考: Java中的低GC:使用原语,而不是来自Vanilla Java的 JCG合作伙伴 Peter Lawrey 的包装 。
- 每个程序员都应该知道的事情
- 正确记录应用程序的10个技巧
- 软件设计法则
- Java最佳实践系列
- 生存在狂野西部开发过程中的9条提示
- 如何在不到1ms的延迟内完成100K TPS
- 提升您的休眠引擎
翻译自: https://www.javacodegeeks.com/2011/07/low-gc-in-java-use-primitives-instead.html