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
这里我每次都在循环中分配新内存,并在再次分配空内存之前在内部/外部循环中执行一些操作。
现在我担心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
我有三个问题: -
答案 0 :(得分:5)
我认为最好使用第一种方法。请注意,HashSet.clear
永远不会缩小哈希表的大小。因此,如果外部循环的第一次迭代向集合中添加了许多元素,则散列表将变得非常大,但是在后续迭代中,即使不需要缩小也需要更少的空间。
另外,第一个版本使得进一步的重构变得更容易:您可能稍后想要将整个内部循环放入单独的方法中。使用第一个版本,您只需将其与HashSet
一起移动。
最后请注意,对于垃圾收集,通常更容易管理短期对象。如果您的HashSet
使用寿命很长,则可能会将其移至旧代,并仅在完整的GC期间删除。
答案 1 :(得分:3)
我建议你坚持第一个变种。这背后的主要原因是保持HashSet变量的范围尽可能小。这样,您实际上确保在迭代结束后它有资格进行垃圾回收。提升它的范围可能会导致其他问题 - 以后可以使用引用来实际更改对象的状态。
此外,如果您在循环内部或外部创建实例,大多数现代Java编译器将生成相同的字节代码。
答案 2 :(得分:3)
我认为每次创建一个新的HashSet更简单,并且以后可能不太容易出现重构错误。除非你有充分的理由重新使用HashSet(垃圾收集暂停对你来说是一个问题,并且分析显示代码的这一部分是原因) - 我会尽可能地保持简单并坚持1.专注于可维护性,应该避免使用Premature Optimization。
答案 3 :(得分:1)
哪一个更快?实际上答案可能因各种因素而异。
版本-1优势:
- 处理器级别的预测分支可能会使此更快。
- 实例的范围仅限于第一个循环。如果引用没有转义,JIT可能实际上编译了你的方法。 GC的工作将会 可能会更容易。
醇>
版本-2:
- 创建新容器的时间更短(坦白说,这并不算太多)。
clear()
是O(n)
- 转义引用可能会阻止JIT进行一些优化。
醇>
选哪一个? 多次测量两个版本的性能。然后,如果您发现显着差异,请更改您的代码,如果没有,请不要执行任何操作:)
答案 4 :(得分:0)
版本2更好 但它会花费更多的时间,但内存性能会很好
答案 5 :(得分:0)
取决于。
回收对象 可用于紧密循环以消除GC压力。特别是当物体对于年轻一代来说太大或者循环运行足够长时间以使其保持稳定时。
但在您的特定示例中,它可能没什么用,因为哈希集仍然包含将在插入时创建的节点对象,并且在清除时符合GC的条件。
另一方面,如果你将这么多项放入集合中,其内部Object[]
数组必须多次调整大小并且对于年轻代而言变得太大,那么回收集合可能是有用的。但在这种情况下,无论如何你应该预先设定一套。
此外,仅在代码块的持续时间内存在的对象可能有资格通过escape analysis进行对象分解/堆栈分配。它们的生命周期越短,触及这些对象的代码路径越不复杂,EA就越有可能成功。
最后它并不重要,直到这个方法实际上成为你的应用程序中的分配热点,在这种情况下它会显示在探查器结果中,你可以采取相应的行动。