关于 MetaSpace 及 FastJSON 导致的 OOM

关于 MetaSpace 内存

在 JDK8 之前,虚拟机内存主要分为堆和非堆两部分,堆中划分新生代老生代,非堆中包含所有非对象信息和运行时信息,其中有一块叫 PermGen,用以保存字节码等类信息。在 JDK8 中取消了这块内存,并添加了 MetaSpace 替代所有功能。

这两块的区别

最大的区别就是位置不同大小不同,PermGen 仍然归属 JVM 内存,一般 32M 或者 64M,都很小,MetaSpace 直接位于本地内存,默认大小只受物理机限制,直到用完物理机内存才抛出 OOM。

所以在某些情况下直接升级 JDK8 可能就出现内存持续增长的情况,在这种情况下通过 top 命令会发现内存猛涨,远超 Xmx 设置的大小, 但通过 jmap 则发现正常。

例如

先看段 fastjson 的代码:

public void process(HttpServletResponse resp) {
    ....
    SerializeConfig config = new SerializeConfig();
    config.put(Long.class, RsLongSerializer.instance);
    resp.getWriter().print(JSON.toJSONString(obj,config));
}

这段代码的问题就在 SerializeConfig 默认会激活 asm,在序列化对象时会为对象生成代理类,然后通过执行代理进行序列化操作,通过这样优化来提高执行性能,但在应用不合理每次新创建 config 的时候就会导致大量生成代码类反而拖慢性能。反序列化时的 ParserConfig 也是同理。

在 jdk8 之前这些代理类会充满 Perm 区导致 FullGC,浪费点 CPU 也不会有大问题,但在 JDK8 中,这些类会大量创建直至充满物理机内存,进而导致进程被系统杀掉。

附 MetaSpace 相关参数

  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
  • -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
  • -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

All Done!

在系统升级的时候还是要谨慎点,一定要做好预研,尽量避免这种由于机制变化导致的系统问题。

2 comments

  1. 你好,请您指教一个问题。
    我在main方法内写了一个循环,通过readLine来进行阻塞,每次释放代码执行1000次,即
    SerializeConfig config = new SerializeConfig();
    config.put(Long.class, RsLongSerializer.instance);
    JSON.toJSONString(obj,config);
    这段代码执行一千次,但是通过jconsole的监控来看,metaspace并无变化,反而是S区一直GC,求指教

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注