Java 是";超出总承包商间接费用限额;失败的第二个原因?
出于这个问题的动机: 最近我和某人就这个错误进行了辩论 在我的理解中,这个错误本身不能被视为JVM失败的“主要”原因 我的意思是,大量的垃圾收集本身并不是失败的原因。大量垃圾收集总是由很少的可用内存量引起的,这会导致频繁的GC调用(核心原因可能是内存泄漏) 如果我正确理解了我对手的位置,他认为系统中产生的许多符合GC’ing条件的小对象导致了它们的频繁收集,是什么导致了这个错误。因此,问题不在于内存泄漏或内存限制低,而在于GC调用频率本身 这里是我们有不同观点的地方 在我的理解中,不管您的流程产生多少符合GC’ing条件的小对象(即使这不是一个好的设计,您可能应该尽可能减少这个数量)。如果您有足够的内存,并且没有明显的内存泄漏,那么在某个时候GC将收集这些对象的大部分,因此这应该不是一个问题。至少这不会导致系统崩溃 简单地恢复我的位置:如果你的GC开销超过了,那么要么是内存泄漏,要么只是需要增加内存限制 简而言之,恢复我对手的位置:如果你制造了大量符合GC’ing条件的小型物体,这已经是一个问题,因为它本身会导致超出GC开销限制 我错了,遗漏了什么吗?-部分答案-Java 是";超出总承包商间接费用限额;失败的第二个原因?,java,garbage-collection,jvm,out-of-memory,heap-memory,Java,Garbage Collection,Jvm,Out Of Memory,Heap Memory,出于这个问题的动机: 最近我和某人就这个错误进行了辩论 在我的理解中,这个错误本身不能被视为JVM失败的“主要”原因 我的意思是,大量的垃圾收集本身并不是失败的原因。大量垃圾收集总是由很少的可用内存量引起的,这会导致频繁的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");
...
}