Java:重用还是重新分配对容器对象的引用?

Java: Reusing vs Reallocating reference to container object?

tl;dr: 在 Java 中,哪个更好,每次重用容器对象或创建对象并让垃圾收集器完成工作

我在 Java 中处理大量数据,我经常使用以下类型的代码结构:-

版本 1:

for(...){//outer loop
   HashSet<Integer> test = new HashSet<>(); //Some container
   for(...){
      //Inner loop working on the above container Data Structure
   }
   //More operation on the container defined above
}//Outer loop ends

这里我每次循环分配新内存,然后在inner/outer循环中做一些操作,然后再分配空内存。

现在我担心 Java 中的内存泄漏。我知道 Java 有一个相当不错的垃圾收集器,但我应该按如下方式修改我的代码而不是依赖它:-

版本 2:

HashSet<Integer> test = null;
for(...){//outer loop
   if(test == null){
      test = new HashSet<>(); //Some container
   }else{
      test.clear()
   }
   for(...){
      //Inner loop working on the above container Data Structure
   }
   //More operation on the container defined above
}//Outer loop ends

我有三个问题:-

  1. 哪个性能更好,或者没有明确的答案。
  2. 第二个版本的时间复杂度会更高吗?换句话说,复杂度为 O(n) 的 clear() 函数 O(1)。我在 javadocs 中没有任何内容。
  3. 这个模式比较常见,哪个版本比较推荐?

我认为最好使用第一种方法。请注意,HashSet.clear 永远不会缩小 hash-table 的大小。因此,如果外循环的第一次迭代将许多元素添加到集合中,散列 table 将变得相当大,但在后续迭代中,即使不需要 space缩小了。

另外,第一个版本使进一步的重构更容易:您稍后可能希望将整个内部循环放入单独的方法中。使用第一个版本,您可以将它与 HashSet.

一起移动

最后请注意,对于垃圾回收,管理短期对象通常更容易。如果你的 HashSet 是长寿命的,它可能会被移动到老年代,并且只在 full GC 期间被移除。

版本 2 更好 但这会花费更多时间,但内存性能会很好

我建议您坚持使用第一个变体。这背后的主要原因是使 HashSet 变量的范围尽可能小。通过这种方式,您实际上可以确保在迭代结束后它将有资格进行垃圾回收。提升它的作用域可能会导致其他问题 - 该引用稍后可用于实际更改对象的状态。

此外,如果您在循环内部或外部创建实例,大多数现代 Java 编译器将生成相同的字节代码。

我认为每次都创建一个新的 HashSet 更简单,并且以后可能不太容易出现重构错误。除非你有充分的理由重新使用 HashSet(垃圾收集暂停对你来说是个问题,分析显示这部分代码是原因) - 我会尽可能简单并坚持 1. 关注可维护性, Premature Optimization 应该避免。

哪个更快?。实际上,答案可能因各种因素而异。

版本 1 的优点:

  1. Predictive branching at processor level might make this faster.
  2. Scope of instance is limited to the first loop. If reference doesn't escape, JIT might actually compile your method. GC's job will probably be easier.

版本-2 :

  1. Less time in creation of new containers (frankly, this is not too much).
  2. clear() is O(n)
  3. Escaped reference might prevent JIT from making some optimizations.

选哪个?. 多次测量两个版本的性能。然后,如果您发现 显着差异,请更改您的代码,如果没有,请不要做任何事情:)

视情况而定。

回收对象可以在紧密循环中有用以消除 GC 压力。特别是当对象对于年轻一代来说太大或者循环运行的时间足够长以至于它被永久使用时。

但在您的特定示例中,它可能没有多大帮助,因为哈希集仍然包含节点对象,这些节点对象将在插入时创建并在清除时符合 GC 条件。

另一方面,如果你把太多的项目放入集合中,它的内部 Object[] 数组必须多次调整大小并且对于年轻一代来说太大了,那么回收放。但在那种情况下,无论如何你都应该预先确定集合的大小。

另外,仅在代码块 期间存在的对象可能 有资格通过 escape analysis 分配对象 decomposition/stack。它们的生命周期越短,接触这些对象的代码路径越不复杂,EA 成功的可能性就越大。

最后这并不重要,直到此方法真正成为您应用程序中的分配热点,在这种情况下,它会显示在分析器结果中,您可以采取相应的行动。