Java 8元空间:OutOfMemoryError:处理反射膨胀问题

3

有很多 链接 和开放的 问答 在网络上,但我仍然缺乏很多信息。

首先要做的事情

问题:

java.lang.OutOfMemoryError: Metaspace

jvm:

java version:  "1.8.0_131"
vm:             Java HotSpot(TM) 64-Bit Server
vm args:       -Xms1024m -Xmx1024m -XX:MaxMetaspaceSize=128m

框架:

Spring, Hibernate, Wicket, Jetty

嫌疑人1:

在使用期间,元空间逐渐增长,并且以下反射类比例地加载到元空间中[由jmap -histo cron jobs观察]

sun.reflect.GeneratedConstructorAccessor1299
sun.reflect.GeneratedMethodAccessor6929
sun.reflect.GeneratedSerializationConstructorAccessor4220

可能的解决方案:

a. 因为我们使用了大量处理反射内容的库,我们认为128m无法容纳metaspace中生成的所有generatedXX类,所以我们计划将metaspace限制加倍。-XX:MaxMetaspaceSize=256m

b. 我们不打算设置以下内容:

-D sun.reflect.noInflation

-D sun.reflect.inflationThreshold

可疑情况2:

即使在达到/占用完全配置的metaspace(128m)之前,Full GC仍在持续运行,并且应用程序变得无响应/缓慢/有时会发生OOM,因为jvm只执行FGC。

[Full GC (Metadata GC Threshold) [PSYoungGen: 224K->0K(698368K)] [ParOldGen: 52910K->52933K(1398272K)] 53134K->52933K(2096640K), [Metaspace: 92733K->92733K(1163264K)], 0.1964143 secs] [Times: user=0.59 sys=0.00, real=0.19 secs]

.

Metaspace使用了147414K,容量为155731K,已分配159616K,保留了1187840K的空间
类空间使用了17242K,容量为19252K,已分配20352K,保留了1048576K的空间

可能的解决方案:

在vm启动时没有明确提到-XX:CompressedClassSpaceSize,这可能导致过度保留地址空间,从而导致误导committed sapce,进而引发Full GC。因此,显式设置-XX:CompressedClassSpaceSize=256m将有助于vm进行正确的内存规划和保留。

问题:

  1. 疑点1:是否有人遇到过类似的问题并得到任何解决方法?
  2. 疑点2:设置-XX:CompressedClassSpaceSize是否真的会对metaspace规划/保留和GC产生影响?有什么指针吗?
  3. 还有其他疑点或建议吗?

-XX:CompressedClassSpaceSize在VM启动时没有明确提到,这可能会导致过度保留地址空间,从而导致误导的已提交空间 - 保留空间不计入已提交数字/RSS/WSS。 - the8472
只需删除“-XX:MaxMetaspaceSize =…”选项即可。元空间最伟大的特点之一是它可以调整大小。如果您想要“帮助VM进行正确的内存规划和保留”,请不要对其施加任意限制。 - Holger
但是当存在真正的类泄漏时,这也带来了更多的风险。 - Sankarganesh Eswaran
1个回答

3
经过花费大量时间,我们发现并没有类泄露的问题,我们是"反射膨胀"的受害者。
package sun.reflect;

/**
 The master factory for all reflective objects, both those in
 java.lang.reflect (Fields, Methods, Constructors) as well as their
 delegates (FieldAccessors, MethodAccessors, ConstructorAccessors).
**/

public class ReflectionFactory {


    // "Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor.newInstance() currently costs
    // 3-4x more than an invocation via native code for the first invocation (though subsequent invocations have been benchmarked
    // to be over 20x faster). Unfortunately this cost increases startup time for certain applications that use reflection
    // intensively (but only once per class) to bootstrap themselves. To avoid this penalty we reuse the existing JVM entry points
    // for the first few invocations of Methods and Constructors and then switch to the bytecode-based implementations.

我亲自验证过,在每16次反射生成时使用了这项技术来提高响应时间。

最后,我们增加了元空间,一切似乎都正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接