【问题标题】:Garbage collection overload, Java垃圾收集重载,Java
【发布时间】:2014-01-22 13:59:30
【问题描述】:

问题在于,由于垃圾收集时间的原因,我在性能上进行了权衡。这个问题可以概括为:

  public void loop(BlockingQueue<Runnable> queue)
  {
  int j = queue.size();
  for(int i =0; i<j;i++)//line2
  {
  Runnable runnable = queue.take();
  runnable.run();//line4

  if(Math.random() > 0.9) System.gc();//line5

  }

  //line7 //will 'runnable = null;' answer the question, logically it looks right

  }

现在通常作为参数传递的队列通常包含超过 40,000 个元素。 而且因为我在循环中遍历队列,即使已经“运行”的对象超出范围,它们仍然不能用于垃圾收集,因为它们在invisible state 中。因此,如果我没有第 5 行,那么当方法退出堆栈时,垃圾收集器会突然产生巨大的负载。想象一下,如果同时有多个线程访问该方法。

我的问题:

  1. 需要第 5 行吗?还有其他替代品吗?
  2. 如果我必须有第 5 行,我发现与没有它相比,它的性能非常非常糟糕。

最终必须进行垃圾收集吗?我不知道什么时候应该发生。

PS:我的电脑上禁用了 Javascript,因此无法评论答案。我将在此处为 cmets 编辑帖子:

@amit:我改了代码,我想你已经明白问题的本质了。代码只是一个示例。

@Tobi:谢谢,但是如何设置更大的堆大小来解决问题。那只是延迟gc的时间。 所以你认为它会在没有手动 gc 的情况下表现最好? 进一步从http://java.sun.com/docs/books/performance/1st_edition/html/JPAppGC.fm.html 开始,它说只有当方法从堆栈中取出时,只有在这种情况下,它才能用于垃圾收集。我尝试使用 finalize() (通过打印,不是正确的方式,但应该至少为 100000 个对象工作一次),绝对没有 gc。

@Paolo:谢谢。我试图实现的是一个流水线模型,其中每个线程都有一个消息队列,基本上是一个框架,任何线程都可以向另一个线程发布一个可运行的(如果它与线程有一些工作),另一个线程将执行他们在一段时间后(空闲时) Ans当我的意思是,当方法从堆栈中出来时重载,我的意思是垃圾收集最终会发生,如果它稍后发生,那么清除40,000个元素将需要很多时间

@ Joachim Sauer : System.gc 可以收集不可见的对象,只是垃圾收集器不会自动收集它们。但是当被强制执行时,它会按照:http://java.sun.com/docs/books/performance/1st_edition/html/JPAppGC.fm.html

【问题讨论】:

  • 我改变了:line3:run->runnable, wueue->queue, 如果我错了就回滚,你实际上是指班级成员,但我认为不是
  • 另外:我怀疑你想从i=0迭代到queue.size(),注意queue.size()在你使用take()时会发生变化,所以如果你有2个元素在队列中,你会'迭代' 仅超过第一个,因为在您 take() 大小更改为 1 和 i&lt;1==false 之后。可能你正在寻找(queue.isEmpty() == false)
  • 你说你概括了这个问题:你能用这个概括的代码重现这个问题吗?如果不是,那么您很可能过于概括,可能需要向我们展示真实代码。
  • 另外:如果您的问题实际上来自不可见的对象(这不太可能,因为您一次只有相对较少的对象),那么将您的代码打包成更小的方法可能 帮助。另外:当System.gc() 有帮助时,那么您的问题几乎可以肯定不是由不可见的物体引起的(因为System.gc() 也无法收集它们!)。
  • 是什么让你认为System.gc() 可以收集隐形物体?链接到的文档从不声称这样的内容。事实上,关于隐形物品可收集性的唯一一句话是以“因为无法收集隐形物品,[...]”开头。

标签: java object garbage-collection


【解决方案1】:

1) 需要第 5 行吗?还有其他替代品吗?

无需调用System.gc()。事实上,调用System.gc() 对吞吐量是有害的。

只需删除第 5 行即可。无需替换任何内容。

2)如果我必须有第 5 行,我发现与没有它相比,性能非常非常糟糕。

这完全符合我的预期。见上文。

而且因为我在循环中遍历队列,即使已经“运行”的对象超出范围,它们仍然不能用于垃圾回收,因为它们处于不可见状态

事实上:

  • 范围外的runnable 变量引用的对象最多只能有一个处于“不可见”状态的对象,并且

  • 强制垃圾回收无论如何都不会回收它。

System.gc 可以收集不可见的对象。

这个说法是错误的。事实上,不可见对象的问题是 GC 无法收集它们。确实,您链接到的文章非常清楚地说明了这一点:

"由于无法收集不可见的对象,这可能是导致内存泄漏的原因。如果遇到这种情况,您可能必须显式地清空您的引用以启用垃圾收藏。”

(已添加重点。)


坦率地说,我认为您正在尝试解决一个不存在的问题。或者如果它确实存在,它与隐形物体无关。

如果不可见对象确实存在问题,那么解决方案就是简单地将null 分配给循环结束时的变量。

【讨论】:

    【解决方案2】:

    从根本上说,您不应该弄清楚何时收集垃圾,这取决于 JVM。第 5 行是不必要的,正如您所发现的那样,它会损害您的应用程序的性能。

    试图反复强制 GC 意味着你的程序设计有问题。

    当方法出栈时,垃圾收集器会突然出现巨大的负载。

    不一定,每次对象超出范围或弹出堆栈帧时,GC 都不会运行。 JVM 决定何时进行 GC 运行,这可能是立即的,也可能是将来的某个时间。

    您想通过这种方法达到什么目的?看起来您有一组要并行执行的任务?如果是这种情况,您应该查看ExecutorService 以便为您将任务放在线程池中。

    【讨论】:

      【解决方案3】:

      让垃圾收集器完成它的工作,并且如有必要(并且仅在那时)使用 VM 参数调整垃圾收集器,但不要在内部循环中调用 System.gc()这个。

      几乎可以保证这样的调用会降低性能,因为它会强制执行完整的垃圾收集周期,而通常垃圾收集器 1) 仅在堆快满时进行收集(因此您可以减少/增加通过为 VM 提供更大/更小的堆来收集),以及 2) 大多数时间只收集最年轻的对象(即generational garbage collection)。

      对于您的特定情况,不可见状态意味着变量 runnable 引用的对象可能仍会在第 7 行被强引用,即使局部变量 runnable 超出范围观点。但是,vm 只为变量 runnable 保留一个槽,在循环的每次迭代中都会重复使用该变量。一旦您从队列中取出下一个元素并将其存储在runnable 中,它将覆盖对前一个元素的引用,从而使该前一个元素有资格进行垃圾回收。换句话说,只有一个元素可能会处于这种不可见状态(每次调用 loop)。

      【讨论】:

      • 请注意,大于应用程序实际需要的最大(和初始)堆大小很多会对性能产生相当严重的负面影响减少局部性(即您需要同时使用的东西分布在许多不同的内存区域,有效地降低了 CPU 拥有的任何内存缓存的有效性)。过多的堆内存也会使 GC 的工作变得更加困难。
      • 只是为了清楚起见 - System.gc() 不会强制执行完整的垃圾回收周期,它只是提示 VM 应该执行一个垃圾回收周期。其余的都是正确的,当调用此方法时通常会执行 ,这会降低性能。
      【解决方案4】:

      只是一些可能有帮助或没有帮助的想法。

      【讨论】: