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

问题

一次线上发版过程中发现应用的接口出现大量的超时现象.通过告警和链路追踪系统发现是有一个应用的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动态对象在方法区中.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352