arthas
arthas copied to clipboard
【分享】如何通过arthas来定位 StackOverflowError?
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.doDispatch
到 Caused 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"
}