【分享】如何通过arthas来定位 StackOverflowError?
- Dominant language
- Java
- Stars
- 37.5k
- Forks
- 7.6k
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 4
Description
tag: user-case
# 如何定位 StackOverflowError
## 示例异常堆栈
```plain
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。
```shell
stack com.alibaba.fastjson.serializer.MapSerializer write \
'@java.lang.Thread@currentThread().getStackTrace().length == 500' -b -n 1
```
## 示例输出
```plain
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"` 出现的位置:
```shell
watch com.xxx.xxx.xxx.fastjson.support.spring.FastJsonHttpMessageConverter write \
'@com.alibaba.fastjson.JSON@toJSONString(params[0])'
-b
```
该case 的单元测试验证如下:
```java
@Test
public void testCircleReference() {
Map f = new HashMap<>();
f.put("name", "father001");
Map 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();
}
}
}
```
第一部分的输出如下:
```json
{
"father":{
"son":{"$ref":"$"},
"name":"father001"
},
"name":"son001"
}
```
Contributor guide
Research direction
The issue provides a complete Chinese troubleshooting article with Arthas commands, stack-trace output, and a Fastjson circular-reference test example, but names no target documentation file. First locate the repository's troubleshooting or user-case documentation structure, then adapt the supplied material to its conventions. Done means the guide is published in the appropriate documentation location with its examples preserved and readable.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100