JSON 序列化导致栈溢出问题-踩坑经验总结- 事故总结集锦 13(一周一更)

表象

1、线上发现栈溢出异常日志,抛到了接口方法的最外层的 catch throwable

image.png

2、查看日志可以看到如下报错

image.png

问题排查定位

1、日志中出现了很多重复错误,猜测可能代码中出现了死循环或者无限递归,但由于是个error 级别的错误被抛到了最外层,以现有日志不好定位是哪里报的异常。从线上日志找到入参后发现在预发布环境可以稳定复现,所以准备在预发布加日志定位异常发生行,最终找 到如下图所示红框中代码行

image.png

2、进一步排查发现主要是在调用 JsonUtils.toStr(items)时发生了异常。

问题分析

1、首先确定下自己封装的 JsonUtils.toStr 使用的 json 转换工具是 fastJson,且转 json 时指定了 SerializerFeature.DisableCircularReferenceDetect 参数,如下图。

image.png

2、查了下 SerializerFeature.DisableCircularReferenceDetec 这个序列化选项,用来关闭引用检测。关闭引用检测后,重复引用对象时就不会被$ref 代替,但是在循环引用时也会导致 StackOverflowError 异常。那么剩下的问题就是就是确定循环引用出现在哪里,目前看只可能是序列化 items 里出现了循环引用。

3、如果对 map 进行序列化就会报出栈溢出。

image.png

解决方法

方法 1:使用 FastJson 时去掉 SerializerFeature.DisableCircularReferenceDetec

方法 2:fastjson 参数不变,去掉对 items(有循环依赖)对象的序列化

总结

  • 之前项目中引用了不同的序列化工具,比如(gosn、fastjson),且正反序列化也有混用的场景。在 fastjson 序列化时,默认会将重复对象解析成$ref,导致了其他序列化工具解析不出来或者解除出来后少数据,所以增加了上边的参数来禁止 fastjson 压缩重复依赖对象,使得压缩后的数据是标准的json数据能够被任何工具解析,但这也导致了异常问题的产生。

  • 所以在线上需要尽量避免同时使用多种 json 工具。除了 Fastjson 之外,我们常用的还有 Jackson 和 Gson 两种序列化工具。将三者进行对比发现,除了 fastjson 外,Jackson 和 Gson 在默认情况下对有循环依赖的对象序列化时都会产生栈溢出,而 fastjson 默认情况下会用$ref 的方式来解决循环引用。解决的方法也都大同小异,基本都是通过增加注解等方法将“环”切断。但如果循环依赖的类是通过其他 jar包引入的类,那加注解的方法是不好用了。

  • 项目中尽量用一种序列化工具,比较推荐 fastjson,虽然安全漏洞多,但性能确实比其他的好很多

  • 在代码中使用序列化工具不要直接依赖 Json 序列化工具的原始类,要自己进行工具类封装,当需要替换序列化工具时可以直接修改自己的封装工具类即可全部替换

  • Json 工具类中尽量要对序列化和反序列化进行 try catch,防止打日志时候的一条异常导致整个业务接口报错。

猜你喜欢

转载自juejin.im/post/7097929153952874526