美文网首页
高并发导致的fastjson OOM问题排查

高并发导致的fastjson OOM问题排查

作者: 鬼谷子的码农之路 | 来源:发表于2019-05-08 17:38 被阅读0次

问题

一次线上发版过程中发现应用的接口出现大量的超时现象.通过告警和链路追踪系统发现是有一个应用的dubbo请求大量超时.速度进入服务器查看日志发现出现大量的OOM:permGen space.执行jstat命令发现有些机器的perm区域使用率达到了100%,频繁的出现fullgc的情况.


image.png

因为没有保留现场的全部命令执行查看过程,以下以问题复现的代码执行过程的查找过程来演示.

问题排查

  1. 问题出现后通过jstat查看jvm的回收情况:
C:\Program Files (x86)\Java\jdk1.7.0_80\bin>jstat.exe -gcutil 6968 3000
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
 65.51   0.00  73.79  79.43  84.92    528    2.940    33    1.125    4.065
  7.56   0.00  66.49  79.52  84.92    530    2.947    33    1.125    4.072
100.00   0.00  16.75  88.73  84.92    532    2.958    33    1.125    4.083
  0.00 100.00  89.65  89.34  84.92    533    2.963    33    1.125    4.089
  0.00  28.91  39.27  89.45  84.92    535    2.970    33    1.125    4.095
  0.00 100.00  12.80  99.16  84.92    537    2.985    33    1.125    4.110
  0.00   0.00  61.73  60.18  84.92    538    2.990    34    1.168    4.159
 61.35   0.00  32.58  60.94  84.92    540    3.000    34    1.168    4.169
  0.00  31.89  79.17  61.02  84.92    541    3.004    34    1.168    4.172
  0.00 100.00  72.83  63.37  85.01    543    3.013    34    1.168    4.182
  0.00 100.00  20.57  72.12  85.01    545    3.028    34    1.168    4.196
 90.98   0.00  91.15  72.49  85.01    546    3.032    34    1.168    4.201
 30.64   0.00  38.54  72.77  85.01    548    3.040    34    1.168    4.208
100.00   0.00   8.84  82.44  85.01    550    3.061    34    1.168    4.229
  0.00 100.00  57.00  89.63  85.01    551    3.071    34    1.168    4.239
  0.00  85.90  27.64  91.46  85.01    553    3.084    34    1.168    4.252
 56.09   0.00  75.19  91.57  85.01    554    3.087    34    1.168    4.255
  7.61   0.00  65.82  91.65  85.01    556    3.093    34    1.168    4.262
  0.00   0.00  14.92  60.00  85.01    558    3.127    35    1.243    4.371
  0.00 100.00  83.44  62.88  85.01    559    3.133    35    1.243    4.376
  0.00  64.12  30.94  68.97  85.01    561    3.146    35    1.243    4.389
 39.64   0.00 100.00  69.05  85.01    562    3.150    35    1.243    4.394
100.00   0.00  46.22  71.22  85.01    564    3.166    35    1.243    4.409
100.00   0.00  16.05  78.76  85.01    566    3.182    35    1.243    4.425
  0.00 100.00  62.86  79.89  85.01    567    3.186    35    1.243    4.429
  1. 通过jmap查看堆对象信息没有什么有用的信息:
C:\Program Files (x86)\Java\jdk1.7.0_80\bin>jmap.exe -histo 6968

 num     #instances         #bytes  class name
----------------------------------------------
   1:        187986        6015552  java.util.TreeMap$Entry
   2:          1232        2802872  [I
   3:         58438        1504824  [Ljava.lang.Object;
   4:         30284        1453632  java.util.TreeMap
   5:         77037        1232592  java.lang.Long
   6:         15572        1134400  [C
   7:          9408        1072320  <constMethodKlass>
   8:          9408         678976  <methodKlass>
   9:         18381         588192  java.io.ObjectStreamClass$WeakClassKey
  10:          1565         586112  <instanceKlassKlass>
  11:          1565         567648  <constantPoolKlass>
  12:         30257         484112  java.util.TreeMap$KeySet
  13:         30246         483936  javax.management.openmbean.CompositeDataSupport
  14:         12543         401376  java.util.LinkedHashMap$Entry
  15:          1490         319552  <constantPoolCacheKlass>
  16:         18754         300064  java.lang.Boolean
  17:         12330         295920  java.lang.String
  18:          1787         263528  [B
  19:         15227         251720  [Ljavax.management.openmbean.CompositeData;
  20:         10802         172832  java.util.Collections$UnmodifiableRandomAccessList
  21:         10799         172784  java.util.Arrays$ArrayList
  22:          1709         157776  java.lang.Class
  23:          9688         155008  java.lang.Integer
  24:          1742         147016  [Ljava.util.HashMap$Entry;
  25:          4045         129440  java.lang.ref.SoftReference
  26:          3938         126016  java.util.TreeMap$KeyIterator
  1. 使用工具arthas 查看classloader发现fastjson的ASMClassLoader对象数异常:
    image.png
  2. 速度做一次堆dump看完整的对象分配情况,通过memoryAnalyzer查看堆信息,找到一个非常有意思的情况:


    图一.png

    从这个堆信息中可以发现有很多Fastjson_ASM生成的动态ASM字节码类信息,但是缺大量的没有关联到实际的堆内对象.好吧,那就开始跟下代码,看看为什么会有这么多的动态对象出现.

问题解决

我们以如下的代码来解析整个过程:

UserBean bean = JSON.parseObject(json, UserBean.class);

跟进去我们可以发现默认的fastjson使用的一个全局的反序列化解析器:


image.png

默认的全局反序列化只对以下类型做了全局的反序列,肯定是没有我们的pojo对象的反序列化解析器的:

primitiveClasses.add(boolean.class);
        primitiveClasses.add(Boolean.class);

        primitiveClasses.add(char.class);
        primitiveClasses.add(Character.class);

        primitiveClasses.add(byte.class);
        primitiveClasses.add(Byte.class);

        primitiveClasses.add(short.class);
        primitiveClasses.add(Short.class);

        primitiveClasses.add(int.class);
        primitiveClasses.add(Integer.class);

        primitiveClasses.add(long.class);
        primitiveClasses.add(Long.class);

        primitiveClasses.add(float.class);
        primitiveClasses.add(Float.class);

        primitiveClasses.add(double.class);
        primitiveClasses.add(Double.class);

        primitiveClasses.add(BigInteger.class);
        primitiveClasses.add(BigDecimal.class);

        primitiveClasses.add(String.class);
        primitiveClasses.add(java.util.Date.class);
        primitiveClasses.add(java.sql.Date.class);
        primitiveClasses.add(java.sql.Time.class);
        primitiveClasses.add(java.sql.Timestamp.class);

        derializers.put(SimpleDateFormat.class, DateFormatDeserializer.instance);
        derializers.put(java.sql.Timestamp.class, TimestampDeserializer.instance);
        derializers.put(java.sql.Date.class, SqlDateDeserializer.instance);
        derializers.put(java.sql.Time.class, TimeDeserializer.instance);
        derializers.put(java.util.Date.class, DateDeserializer.instance);
        derializers.put(Calendar.class, CalendarCodec.instance);

        derializers.put(JSONObject.class, JSONObjectDeserializer.instance);
        derializers.put(JSONArray.class, JSONArrayDeserializer.instance);

        derializers.put(Map.class, MapDeserializer.instance);
        derializers.put(HashMap.class, MapDeserializer.instance);
        derializers.put(LinkedHashMap.class, MapDeserializer.instance);
        derializers.put(TreeMap.class, MapDeserializer.instance);
        derializers.put(ConcurrentMap.class, MapDeserializer.instance);
        derializers.put(ConcurrentHashMap.class, MapDeserializer.instance);

        derializers.put(Collection.class, CollectionDeserializer.instance);
        derializers.put(List.class, CollectionDeserializer.instance);
        derializers.put(ArrayList.class, CollectionDeserializer.instance);

        derializers.put(Object.class, JavaObjectDeserializer.instance);
        derializers.put(String.class, StringCodec.instance);
        derializers.put(StringBuffer.class, StringCodec.instance);
        derializers.put(StringBuilder.class, StringCodec.instance);
        derializers.put(char.class, CharacterCodec.instance);
        derializers.put(Character.class, CharacterCodec.instance);
        derializers.put(byte.class, NumberDeserializer.instance);
        derializers.put(Byte.class, NumberDeserializer.instance);
        derializers.put(short.class, NumberDeserializer.instance);
        derializers.put(Short.class, NumberDeserializer.instance);
        derializers.put(int.class, IntegerCodec.instance);
        derializers.put(Integer.class, IntegerCodec.instance);
        derializers.put(long.class, LongCodec.instance);
        derializers.put(Long.class, LongCodec.instance);
        derializers.put(BigInteger.class, BigIntegerCodec.instance);
        derializers.put(BigDecimal.class, BigDecimalCodec.instance);
        derializers.put(float.class, FloatCodec.instance);
        derializers.put(Float.class, FloatCodec.instance);
        derializers.put(double.class, NumberDeserializer.instance);
        derializers.put(Double.class, NumberDeserializer.instance);
        derializers.put(boolean.class, BooleanCodec.instance);
        derializers.put(Boolean.class, BooleanCodec.instance);
        derializers.put(Class.class, ClassDerializer.instance);
        derializers.put(char[].class, CharArrayDeserializer.instance);

        derializers.put(AtomicBoolean.class, BooleanCodec.instance);
        derializers.put(AtomicInteger.class, IntegerCodec.instance);
        derializers.put(AtomicLong.class, LongCodec.instance);
        derializers.put(AtomicReference.class, ReferenceCodec.instance);

        derializers.put(WeakReference.class, ReferenceCodec.instance);
        derializers.put(SoftReference.class, ReferenceCodec.instance);

        derializers.put(UUID.class, UUIDCodec.instance);
        derializers.put(TimeZone.class, TimeZoneCodec.instance);
        derializers.put(Locale.class, LocaleCodec.instance);
        derializers.put(Currency.class, CurrencyCodec.instance);
        derializers.put(InetAddress.class, InetAddressCodec.instance);
        derializers.put(Inet4Address.class, InetAddressCodec.instance);
        derializers.put(Inet6Address.class, InetAddressCodec.instance);
        derializers.put(InetSocketAddress.class, InetSocketAddressCodec.instance);
        derializers.put(File.class, FileCodec.instance);
        derializers.put(URI.class, URICodec.instance);
        derializers.put(URL.class, URLCodec.instance);
        derializers.put(Pattern.class, PatternCodec.instance);
        derializers.put(Charset.class, CharsetCodec.instance);
        derializers.put(Number.class, NumberDeserializer.instance);
        derializers.put(AtomicIntegerArray.class, AtomicIntegerArrayCodec.instance);
        derializers.put(AtomicLongArray.class, AtomicLongArrayCodec.instance);
        derializers.put(StackTraceElement.class, StackTraceElementDeserializer.instance);

        derializers.put(Serializable.class, JavaObjectDeserializer.instance);
        derializers.put(Cloneable.class, JavaObjectDeserializer.instance);
        derializers.put(Comparable.class, JavaObjectDeserializer.instance);
        derializers.put(Closeable.class, JavaObjectDeserializer.instance);

所有后续的任何调用,使用:

ObjectDeserializer derializer = derializers.get(type);

这个方式来获取我们对象的反序列化解析器的结构都应该是为null的.最后会走到代码块1中:

 derializer = createJavaBeanDeserializer(clazz, type);
image.png

并最终通过asmFactory来进行对象的asm动态对象生成:

return asmFactory.createJavaBeanDeserializer(this, clazz, type);

重点来了,这个asm生成的动态对象会在方法区中生成我们的对象信息,所以我们看到FastJsaon_ASM对象.但是为什么会生成多呢?起始答案已经呼之欲出了.
我们前面的图片中看到asmFactory创建完成动态代理对象后会返回反序列化解释器并放入解析器的Map集合中,即如下语句(上图的代码块2):

putDeserializer(type, derializer);

这个就意味着,fastJson是先去调用asm生成动态代理对象,然后再把解释器设置到解析器的Map集合中,并发情况就有可能会出现多个线程进来解析器的Map集合中都没有我们对象的解析器的情况,所以就会出现多次调用asm的情况.

注意: 我们使用的比较老的fastjson版本

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.6</version>
        </dependency>

在这个版本中,fastjson会对我们的pojo对象和这个对象的每个属性都生成asm的动态对象,所以当你的并发高,对象属性又多的时候,在方法区中的对象会呈现一种爆炸式的上涨.
在fastjson的新版本中,已经去掉了给方法生成asm动态对象,所以相对会安全很多.但是依然会有无用的asm动态对象在方法区中.

相关文章

  • 高并发导致的fastjson OOM问题排查

    问题 一次线上发版过程中发现应用的接口出现大量的超时现象.通过告警和链路追踪系统发现是有一个应用的dubbo请求大...

  • Android内存优化—内存优化总结

    内存问题 内存抖动:导致GC导致卡顿 内存泄漏:导致频繁GC,可用内存减少 内存溢出:导致OOM 工具排查 AS中...

  • OOM问题排查

    JVM参数设置 在JAVA_OPTIONS变量中增加参数 说明: -XX:+HeapDumpOnOutOfMemo...

  • zookeeper oom

    一:问题: zookeeper oom挂掉之后重启及选举失败 二:排查: 2.1 oom信息: 其中,FileTx...

  • 关于 MetaSpace 及 FastJSON 导致的 OOM

    关于 MetaSpace 内存 在 JDK8 之前,虚拟机内存主要分为堆和非堆两部分,堆中划分新生代老生代,非堆中...

  • Redis OOM问题排查

    1. 问题描述 看到Redis报了OOM的错误,而且服务响应速度非常慢,页面上丢了很多数据,赶紧起来查看问题。 2...

  • OOM问题排查方法

    根据日志确定发生OOM的原因和区域,以下几个内存区域都可能发生OOM,先找到打印出的OOM错误日志和dump文件(...

  • [Redis] OOM 问题排查

    发现 在 jedis 写入 key 时发现了以下异常日志(部分) 排查 服务器内存是否满了 redis 内存限制是...

  • Android 内存优化简介

    1、内存占用高导致的问题 1、内存泄漏导致OOM崩溃 2、界面卡顿,影响用户体验 3、高内存耗电,被系统及安全软件...

  • 自己项目中用到的obj 转hashMap

    需求背景:项目中原来是通过fastJson 进行转换的,但是这个方法在vivo51上会卡死手机,最后导致OOM。 ...

网友评论

      本文标题:高并发导致的fastjson OOM问题排查

      本文链接:https://www.haomeiwen.com/subject/shzmoqtx.html