Java 是";超出总承包商间接费用限额;失败的第二个原因?

Java 是";超出总承包商间接费用限额;失败的第二个原因?,java,garbage-collection,jvm,out-of-memory,heap-memory,Java,Garbage Collection,Jvm,Out Of Memory,Heap Memory,出于这个问题的动机: 最近我和某人就这个错误进行了辩论 在我的理解中,这个错误本身不能被视为JVM失败的“主要”原因 我的意思是,大量的垃圾收集本身并不是失败的原因。大量垃圾收集总是由很少的可用内存量引起的,这会导致频繁的GC调用(核心原因可能是内存泄漏) 如果我正确理解了我对手的位置,他认为系统中产生的许多符合GC’ing条件的小对象导致了它们的频繁收集,是什么导致了这个错误。因此,问题不在于内存泄漏或内存限制低,而在于GC调用频率本身 这里是我们有不同观点的地方 在我的理解中,不管您的流程产

出于这个问题的动机:

最近我和某人就这个错误进行了辩论

在我的理解中,这个错误本身不能被视为JVM失败的“主要”原因

我的意思是,大量的垃圾收集本身并不是失败的原因。大量垃圾收集总是由很少的可用内存量引起的,这会导致频繁的GC调用(核心原因可能是内存泄漏)

如果我正确理解了我对手的位置,他认为系统中产生的许多符合GC’ing条件的小对象导致了它们的频繁收集,是什么导致了这个错误。因此,问题不在于内存泄漏或内存限制低,而在于GC调用频率本身

这里是我们有不同观点的地方

在我的理解中,不管您的流程产生多少符合GC’ing条件的小对象(即使这不是一个好的设计,您可能应该尽可能减少这个数量)。如果您有足够的内存,并且没有明显的内存泄漏,那么在某个时候GC将收集这些对象的大部分,因此这应该不是一个问题。至少这不会导致系统崩溃

简单地恢复我的位置:如果你的GC开销超过了,那么要么是内存泄漏,要么只是需要增加内存限制

简而言之,恢复我对手的位置:如果你制造了大量符合GC’ing条件的小型物体,这已经是一个问题,因为它本身会导致超出GC开销限制

我错了,遗漏了什么吗?

-部分答案-

注意,我使用OpenJDK(JDK 9)源作为一个基础来评论这个问题。这个答案不依赖于任何类型的文档或已发布的规范,并且包含了一些来自我对源代码的理解和解释的推测

超出的
GC开销限制在VM中被视为内存不足错误的子类型,并在尝试分配内存失败后生成(请参阅(a))

基本上,VM跟踪完全垃圾回收的发生次数,并将其与为完全GC强制执行的限制进行比较(可以使用
-XX:GCTimeLimit=
,cf在热点上配置)

如何跟踪完整GC计数的实现以及检测到GC开销限制时的逻辑在中的一个位置可用。如您所见,旧代和eden代中可用内存的两个附加条件需要满足GC开销限制的标准:

void AdaptiveSizePolicy::check_gc_overhead_limit(
                                      size_t young_live,
                                      size_t eden_live,
                                      size_t max_old_gen_size,
                                      size_t max_eden_size,
                                      bool   is_full_gc,
                                      GCCause::Cause gc_cause,
                                      CollectorPolicy* collector_policy) {
  ...
  if (is_full_gc) {
    if (gc_cost() > gc_cost_limit &&
      free_in_old_gen < (size_t) mem_free_old_limit &&
      free_in_eden < (size_t) mem_free_eden_limit) {
      // Collections, on average, are taking too much time, and
      //      gc_cost() > gc_cost_limit
      // we have too little space available after a full gc.
      //      total_free_limit < mem_free_limit
      // where
      //   total_free_limit is the free space available in
      //     both generations
      //   total_mem is the total space available for allocation
      //     in both generations (survivor spaces are not included
      //     just as they are not included in eden_limit).
      //   mem_free_limit is a fraction of total_mem judged to be an
      //     acceptable amount that is still unused.
      // The heap can ask for the value of this variable when deciding
      // whether to thrown an OutOfMemory error.
      // Note that the gc time limit test only works for the collections
      // of the young gen + tenured gen and not for collections of the
      // permanent gen.  That is because the calculation of the space
      // freed by the collection is the free space in the young gen +
      // tenured gen.
      // At this point the GC overhead limit is being exceeded.
      inc_gc_overhead_limit_count();
      if (UseGCOverheadLimit) {
        if (gc_overhead_limit_count() >= AdaptiveSizePolicyGCTimeLimitThreshold){
          // All conditions have been met for throwing an out-of-memory
          set_gc_overhead_limit_exceeded(true);
          // Avoid consecutive OOM due to the gc time limit by resetting
          // the counter.
          reset_gc_overhead_limit_count();
      } else {
        ...
      }
(b) 关于G1实现的注意事项 查看G1实现的方法
mem\u allocate
(可在
g1CollectedHeap.cpp
中找到),似乎不再使用布尔值
gc\u开销限制。如果启用G1 GC,我不会太快得出GC内存开销错误不再发生的结论-我需要检查这一点

结论
  • 看来你是对的,这个错误确实来自于记忆耗尽

  • 这个错误可以根据收集小对象的次数生成的论点在我看来并不正确,因为

  • 我们看到VM确实需要耗尽内存才能发生此错误
  • 与第一个原因无关,我们无论如何都需要进一步细化语句,尤其是对小对象的引用。我们谈论的是年轻一代的收藏吗?如果是这样,这些集合将不包括在根据限制检查的GC计数中,因此,无论VM是否运行OOM,都不会有机会卷入此错误

或者您的CPU电源太少。是否曾尝试在24核机器上创建超出了GC开销限制的
GC?在这样的场景中,我看到了几分钟的GC时间。结论:错误表明资源/系统/JVM有问题。究竟什么取决于具体机器上的具体程序->IMHO,这通常无法回答。我不认为增加内存限制是最终的解决方案。例如,当OOE是由过多的碎片引起时,更多的内存可能会随着时间的推移而变得更糟。你所有的评论都是非常有价值的。最好至少收到一个聚合的完整解释应答器AFAIK GC开销限制超出了次要收集(小对象…)无法触发的限制-看来你赢了,因为你的对手完全错了,因为GC的性能不取决于无法访问对象的数量。标记和复制/压缩都必须处理可到达的对象,因此在大多数情况下,情况正好相反,死对象越多,垃圾收集器的工作量就越小。这些收集不包括在根据限制检查的GC计数中。这基本上总结了反对这两种方法的论点+1.
HeapWord* CollectedHeap::common_mem_allocate_noinit(KlassHandle klass, size_t size, TRAPS) {
    ...
    bool gc_overhead_limit_was_exceeded = false;
    result = Universe::heap()->mem_allocate(size, &gc_overhead_limit_was_exceeded);
    ...
    // Failure cases
   if (!gc_overhead_limit_was_exceeded) {
       report_java_out_of_memory("Java heap space");
       ...
    } else {
       report_java_out_of_memory("GC overhead limit exceeded");
       ...
    }