alibaba / arthas

Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas
https://arthas.aliyun.com/
Apache License 2.0
35.71k stars 7.51k forks source link

【分享】如何通过arthas来定位 StackOverflowError? #2893

Open btpka3 opened 2 months ago

btpka3 commented 2 months ago

tag: user-case

如何定位 StackOverflowError

示例异常堆栈

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1082)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
  ...
Caused by: java.lang.StackOverflowError
    at com.alibaba.fastjson.serializer.JSONSerializer.setContext(JSONSerializer.java:136)
    at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:83)
    at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:44)
    at com.alibaba.fastjson.serializer.ListSerializer.write(ListSerializer.java:135)
    at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:271)
    at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:44)
    at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:271)
  ... // 以下均是类似循环。

发生 StackOverflowError 时,堆栈里往往看不到是哪里触发了该异常,比如上面的case中,从 DispatcherServlet.doDispatchCaused by: java.lang.StackOverflowError 之间发生了什么?看不出来。

思路

示例arthas命令 下面的case是判断调用堆栈深度500。

stack com.alibaba.fastjson.serializer.MapSerializer write \
  '@java.lang.Thread@currentThread().getStackTrace().length == 500' -b -n 1

示例输出

Press Q or Ctrl+C to abort.
Affect(class count: 14 , method count: 28) cost in 6407 ms, listenerId: 20
ts=2024-09-05 23:00:55;thread_name=http-nio-7001-exec-11;id=1549;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@408d5c64;trace_id=2107569217255484514404015e3ba7;rpc_id=0.1.1
    @com.alibaba.fastjson.serializer.MapSerializer.write()
        at com.alibaba.fastjson.serializer.ListSerializer.write(ListSerializer.java:135)
        at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:271)
        at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:44)
        at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:271)
        ...
        at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:271)
        at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:44)
        at com.alibaba.fastjson.serializer.FieldSerializer.writeValue(FieldSerializer.java:318)
        at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:472)
        at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:154)
        at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:312)
        at com.alibaba.fastjson.JSON.writeJSONStringWithFastJsonConfig(JSON.java:1059)
        at com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter.writeInternal(FastJsonHttpMessageConverter.java:314)
        at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:227)
                       # ⭕️⭕️⭕️ 异常发生点
        at com.xxx.xxx.xxx.fastjson.support.spring.FastJsonHttpMessageConverter.write(FastJsonHttpMessageConverter.java:246)
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:183)
        at com.xxx.xxx.xxx.JsonEscapeReturnValueHandler.handleReturnValue(JsonEscapeReturnValueHandler.java:50)
        at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:135)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:665)

定位到异常点之后,就可以review相关代码,再配合该行进行watch 进行具体的分析了。


PS: 这里的case 是 启用了 fastjson 1.x 的 SerializerFeature.DisableCircularReferenceDetect 特性后,关闭循环引用检测,再遇到有循环引用的数据造成的。 fastjson 1.x 默认是不使用该功能的。故可以保持默认输出下参数,检测 "$ref" 出现的位置:

watch com.xxx.xxx.xxx.fastjson.support.spring.FastJsonHttpMessageConverter write \
'@com.alibaba.fastjson.JSON@toJSONString(params[0])'  
-b

该case 的单元测试验证如下:

@Test
public void testCircleReference() {
    Map<String, Object> f = new HashMap<>();
    f.put("name", "father001");
    Map<String, Object> s = new HashMap<>();
    s.put("name", "son001");

    f.put("son", s);
    s.put("father", f);
    // WORKS
    {
        String str = JSON.toJSONString(
                s,
                SerializerFeature.PrettyFormat
        );
        System.out.println(str);
    }
    // ERROR
    {
        // 如果有循环依赖,且使用了 SerializerFeature.DisableCircularReferenceDetect 属性,则会
        // 抛出异常
        try {
            JSON.toJSONString(
                    s,
                    SerializerFeature.DisableCircularReferenceDetect,
                    SerializerFeature.PrettyFormat
            );
            Assertions.fail("should throw exception");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

第一部分的输出如下:

{
    "father":{
        "son":{"$ref":"$"},
        "name":"father001"
    },
    "name":"son001"
}
lxyyouxiang123 commented 1 month ago

在发生异常的点的地方向上打印 (通过stack指令) 预估栈深度可以见到应用层代码栈 就可以知道自己代码错误点