Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
btpka3 opened this issue Sep 5, 2024 · 1 comment
Open

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

btpka3 opened this issue Sep 5, 2024 · 1 comment

Comments

@btpka3
Copy link

btpka3 commented Sep 5, 2024

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 watch 命令 使用 -b(在方法调用前)执行
  • 通过当前调用堆栈的深度大于某个阈值,在实际发生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
Copy link

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

@hengyunabc hengyunabc changed the title 【分享】如何通过arthas来定义 StackOverflowError? 【分享】如何通过arthas来定位 StackOverflowError? Oct 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants