性能文章>惊:Dubbo居然有必现StackOverflowError的Bug>

惊:Dubbo居然有必现StackOverflowError的Bug原创

2年前
741203

说明:本文场景基于dubbo-2.5.3版本。

如果你对StackOverflowError有一定的了解,就可以知道出现这个问题的主要原因就是调用栈太深,比如常见的无限递归调用。那本文要介绍的Dubbo抛出的这个StackOverflowError又是什么原因呢?且往下看。

  • 重现问题

话不多说,直入主题。这次碰到的StackOverflowError非常好重现,只需要如下简短的代码即可。需要注意的是这里调用的是com.alibaba.dubbo.common.json.JSON,而不是fastjson中的com.alibaba.fastjson.JSON:

package com.afei.test.dubbo.provider.main;

import com.alibaba.dubbo.common.json.JSON;
import java.util.Locale;

public class DubboTest {
    public static void main(String[] args) throws Exception {
        Locale locale = Locale.getDefault();
        System.out.println(JSON.json(locale));
    }
}

运行这段代码能得到如下异常:

Exception in thread "main" java.lang.StackOverflowError
    at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
    at sun.util.locale.provider.LocaleResources.getLocaleName(LocaleResources.java:233)
    at java.util.Locale.getDisplayName(Locale.java:1879)
    at java.util.Locale.getDisplayName(Locale.java:1845)
    at com.alibaba.dubbo.common.bytecode.Wrapper0.getPropertyValue(Wrapper0.java)
    at com.alibaba.dubbo.common.json.GenericJSONConverter.writeValue(GenericJSONConverter.java:125)
    at com.alibaba.dubbo.common.json.GenericJSONConverter.writeValue(GenericJSONConverter.java:73)
    at com.alibaba.dubbo.common.json.GenericJSONConverter.writeValue(GenericJSONConverter.java:129)
    at com.alibaba.dubbo.common.json.GenericJSONConverter.writeValue(GenericJSONConverter.java:73)
    at com.alibaba.dubbo.common.json.GenericJSONConverter.writeValue(GenericJSONConverter.java:129)
    at com.alibaba.dubbo.common.json.GenericJSONConverter.writeValue(GenericJSONConverter.java:73)
  • 分析原因

由这个异常堆栈信息,我们很容易知道在GenericJSONConverter中的第73行和129行之间出现了无限递归调用,打开dubbo源码并debug,发现在调用GenericJSONConverter中的writeValue()方法时,首先会判断需要序列化的对象的类型。当对象是如下类型时会特殊处理:

  1. 原生类型或者封装类型;
  2. JSONNode类型;
  3. 枚举;
  4. 数组;
  5. Map;
  6. 集合类型;

如果需要序列化的对象是其他类型,比如这里的Locale类型,序列化逻辑如下所示:

jb.objectBegin();

Wrapper w = Wrapper.getWrapper(c);
// 得到这个对象的所有属性
String pns[] = w.getPropertyNames();
// 遍历属性
for( String pn : pns )
{
  // 被序列化的对象Locale并不是Throwable类型,忽略这段逻辑
  if ((obj instanceof Throwable) && (
      "localizedMessage".equals(pn) 
      || "cause".equals(pn) 
      || "stackTrace".equals(pn))) {
    continue;
  }

  jb.objectItem(pn);
  // 得到当前遍历属性的值
  Object value = w.getPropertyValue(obj,pn);
  if( value == null || value == obj)
    jb.valueNull();
  else
    // 无限递归死循环出现在这里
    writeValue(value, jb, writeClass);
}

通过这段源码的分析,我们大概可以知道Locale的属性中肯定有Locale类型的属性。由于有Locale类型的属性,导致继续调用GenericJSONConverter中的writeValue()方法,从而无限递归下去,让我们继续Debug源码验证这个猜想。

Debug到String pns[] = w.getPropertyNames();,我们通过查看Locale的属性pns[]可以验证我们前面的猜想,如下图所示。Locale属性availableLocales的类型还是Locale,从而出现死循环直到抛出StackOverflowError:

image.png

  • 解决问题

那么如何解决这个问题呢?很简单,不要使用dubbo中的JSON,改为使用fastjson中的JSON,或者jackson和GSON都可以:

Locale locale = Locale.getDefault();
System.out.println(com.alibaba.fastjson.JSON.toJSON(locale));
System.out.println(new com.google.gson.Gson().toJson(locale));

image.png

  • Dubbo Fix

笔者翻看dubbo issue历史,发现dubbo在2018-05-09修复了这个问题,对应的dubbo版本是2.6.3,描述为:add Locale serialize & deserialize support。pull地址如下:https://github.com/apache/dubbo/pull/1761/commits。

修复的代码片段如下所示,主要改动点有:

  1. 如果序列化对象是Locale类型,那么序列化方式就是调用toString()方法;
  2. 如果反序列化目标对象类型是Locale,那么将value以下划线_分割,然后构造Locale对象,用法参考:JSON.parse(“zh_CN”, Locale.class);
    image.png
请先登录,再评论

暂无回复,快来写下第一个回复吧~

为你推荐

在调试器里看LINUX内核态栈溢出
图灵最先发明了栈,但没有给它取名字。德国人鲍尔也“发明”了栈,取名叫酒窖。澳大利亚人汉布林也“发明”了栈,取名叫弹夹。1959年,戴克斯特拉在度假时想到了Stack这个名字,后来被广泛使用。
惊:Dubbo居然有必现StackOverflowError的Bug
说明:本文场景基于dubbo-2.5.3版本。 如果你对StackOverflowError有一定的了解,就可以知道出现这个问题的主要原因就是调用栈太深,比如常见的无限递归调用。那本文要介绍的Dub
JVM源码分析之栈溢出完全解读
概述之所以想写这篇文章,其实是因为最近有不少系统出现了栈溢出导致进程crash的问题,并且很隐蔽,根本原因还得借助coredump才能分析出来,于是想从JVM实现的角度来全面分析下栈溢出的这类问题,或
What?一个 Dubbo 服务启动要两个小时!
前言前几天在测试环境碰到一个非常奇怪的与 ```dubbo``` 相关的问题,事后我在网上搜索了一圈并没有发现类似的帖子或文章,于是便有了这篇。希望对还未碰到或正在碰到的朋友有所帮助。 现象现象是这样
一次StackOverflowError排查,原因竟然和Dubbo有关!
前言某天业务方的同事和我反馈,说系统出现了StackOverflowError.坦白说,Exception见得过了,但是Error倒是很少出现,此时他的心情是这样的 一波猛如虎的操作我们先来看血淋淋的
又踩到Dubbo的坑,但是这次我笑不出来
前言直入主题,线上应用发现,偶发性出现如下异常日志。当然由于线上具体异常包含信息量过大,秉承让肥朝的粉丝没有难调试的代码的原则,我特意抽取了一个复现的demo放在了git,让你不在现场,一样享受到排查
用隧道协议实现不同dubbo集群间的透明通信
前言笔者最近完成了一个非常有意思的隧道机制(已在产线运行),可以让注册到不同zookeeper之间的dubbo集群之间能够正常进行通信。如下图所示: 例如图中A/B两个网络隔离的集群,两者只能通过专线
Dubbo应用无法重连zookeeper
前言dubbo是一个成熟且被广泛运用的框架。饶是如此,在某些极端条件下基于dubbo的应用还会出现无法重连zookeeper的问题。由于此问题容易导致比较大的故障,所以笔者费了一番功夫去定位,现将排查