今天是中秋节,一个团圆与美好的日子,也是我长久以来心愿达成的时刻。我内心深处一直怀揣着撰写一篇深入剖析OOM(Out Of Memory,内存溢出)问题定位与解决策略文章的愿望。在这个特别的日子里,我终于能够放下繁忙,沉浸在思考与写作之中,将这份心愿化为了现实。愿这篇文章能像中秋的明月一样,照亮开发者们在解决OOM问题时的道路,带来清晰与指引。同时,也借此机会向大家送上最真挚的中秋祝福,愿大家在这个团圆的日子里,与家人共赏明月,共享天伦之乐,幸福安康,中秋快乐!
模拟发生OOM的代码:
@RestController
public class MemoryOverflowController {
private static final Logger log = LoggerFactory.getLogger(MemoryOverflowController.class);
@Autowired
private UserService userService;
/**
* Java Heap Space OOM
* Java 堆空间不足,通常是由于创建了大量对象或存在内存泄漏。
*/
@PostMapping("/OOM/test1")
public void test1() {
log.info("开始请求模拟:【Java Heap Space OOM】循环创建对象");
List<User> userList = new ArrayList<>();
while (true) {
// 不断创建对象,直到堆空间耗尽
userList.add(new User());
}
}
@PostMapping("/OOM/test2")
public void test2() {
log.info("开始请求模拟:【Java Heap Space OOM】sql查询数据量过大导致OOM");
userService.listUser();
}
/**
* Direct Buffer Memory OOM
* 直接内存(堆外内存)不足,通常是由于频繁分配直接缓冲区而未能及时释放。
*
* 调整参数:使用 JVM 参数 -XX:MaxDirectMemorySize=10m(设置直接内存为 10MB)来快速触发 OOM。
*/
@PostMapping("/OOM/test3")
public void test3() {
log.info("开始请求模拟:【Direct Buffer Memory OOM】");
List<ByteBuffer> buffers = new ArrayList<>();
while (true) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024); // 每次分配 1MB 的直接缓冲区
buffers.add(buffer); // 保存缓冲区引用以防止垃圾回收
}
}
/**
* GC Overhead Limit Exceeded
* GC 花费了过多时间(超过 98%)而回收的内存不足(少于 2%),通常是由于过多的小对象或无效的大对象占用了堆空间。
*
* 可以使用 -Xmx10m -XX:+UseParallelGC 来增加发生此错误的可能性。
*/
@PostMapping("/OOM/test4")
public void test4() {
log.info("开始请求模拟:【GC Overhead Limit Exceeded】");
Map<Integer, String> map = new HashMap<>();
int i = 0;
while (true) {
// 不断放入大量小对象导致频繁 GC
map.put(i, String.valueOf(i++));
}
}
/**
* Unable to Create New Native Thread
* 系统无法分配新的本地线程,可能是因为操作系统资源耗尽或 JVM 线程数限制。
*
* 调整 -Xss1m(每个线程的堆栈大小)或操作系统的线程数限制。
*/
@PostMapping("/OOM/test5")
public void test5() {
log.info("开始请求模拟:【Unable to Create New Native Thread】");
while (true) {
new Thread(() -> {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start(); // 不断创建新线程,直到系统无法再创建
}
}
}
将jar包上传至服务器。
shenyulong@shenyulongdeMacBook-Pro target % scp cloud_2024-0.0.1-SNAPSHOT.jar root@192.*.*.*:/app/java_jar
root@192.*.*.*'s password:
cloud_2024-0.0.1-SNAPSHOT.jar 100% 31KB 5.8MB/s 00:00
shenyulong@shenyulongdeMacBook-Pro target %
方便每次启动,写一个启动脚本。
#!/bin/sh
echo "==============【点火】=============="
# 启动 Java 应用,并将日志输出到 nohup.out 文件中
nohup java -Xmx20M \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/app/java_jar/to/dump \
-XX:MaxDirectMemorySize=10m \
-XX:+UseParallelGC \
-Xss1m \
-jar cloud_2024-0.0.1-SNAPSHOT.jar > nohup.out 2>&1 &
echo "==============【发射成功】=============="
通用 JVM 参数解释
• -Xmx<size>:设置 JVM 最大堆内存大小。
• -Xms<size>:设置 JVM 初始堆内存大小(可选)。
• -XX:MaxMetaspaceSize=<size>:设置 Metaspace 的最大大小。
• -XX:MaxDirectMemorySize=<size>:设置最大直接内存大小(堆外内存)。
• -Xss<size>:设置每个线程的堆栈大小。
• -XX:+HeapDumpOnOutOfMemoryError:启用在 OOM 时生成 Heap Dump 文件,以便后续分析。
请求接口:【/OOM/test1】异常日志:
九月 13, 2024 8:14:08 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-09-13 20:14:08.948 [http-nio-8080-exec-1] INFO com.syl.cloud_2024.controller.MemoryOverflowController - 开始请求模拟:【Java Heap Space OOM】循环创建对象
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /app/java_jar/to/dump/java_pid21090.hprof ...
Heap dump file created [35060211 bytes in 0.109 secs]
九月 13, 2024 8:14:09 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space] with root cause
java.lang.OutOfMemoryError: Java heap space
at com.syl.cloud_2024.controller.MemoryOverflowController.test1(MemoryOverflowController.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
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:1071)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
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:696)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
通过日志可以明显的看出问题是发生在 MemoryOverflowController类的38行,而38行正是我们循环创建User对象的方法。
结论:
当 Java 堆空间不足时,OOM 错误会有详细的堆栈跟踪信息,通常可以精确到具体的代码行,例如某个集合的扩展、对象创建等。
原因:
堆空间不足,通常是由于过多的对象无法被垃圾回收。
MAT 排查方式:
• Overview 页面:首先查看内存使用的概览,找出最大的对象类型。
• Dominators Tree:查看内存中占用最大的对象或对象树,找到保留最多内存的根对象。
• Leak Suspects Report:使用 MAT 的自动报告功能,帮助识别可能的内存泄漏点。
• Histogram:查看哪些对象占用了最多的堆空间,关注可疑的大型集合(如 ArrayList, HashMap)。
• Path to GC Roots:追踪从 GC 根节点到大对象的引用路径,识别导致对象无法被回收的引用链。
请求接口:【/OOM/test2】异常日志:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /app/java_jar/to/dump/java_pid23650.hprof ...
Heap dump file created [36503227 bytes in 0.116 secs]
九月 13, 2024 8:48:04 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.TransientDataAccessResourceException:
### Error querying database. Cause: java.sql.SQLException: Java heap space
### The error may exist in com/syl/cloud_2024/mapper/UserMapper.java (best guess)
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT id,name,mobile,location FROM user
### Cause: java.sql.SQLException: Java heap space
; Java heap space; nested exception is java.sql.SQLException: Java heap space] with root cause
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:267)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:241)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:233)
at java.util.ArrayList.add(ArrayList.java:464)
at com.mysql.cj.protocol.a.TextResultsetReader.read(TextResultsetReader.java:85)
at com.mysql.cj.protocol.a.TextResultsetReader.read(TextResultsetReader.java:48)
at com.mysql.cj.protocol.a.NativeProtocol.read(NativeProtocol.java:1598)
at com.mysql.cj.protocol.a.NativeProtocol.readAllResults(NativeProtocol.java:1652)
at com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol.java:961)
at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1075)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:930)
at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:370)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:493)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:64)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
at com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor.doQuery(MybatisSimpleExecutor.java:69)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
at com.baomidou.mybatisplus.core.executor.MybatisCachingExecutor.query(MybatisCachingExecutor.java:165)
at com.baomidou.mybatisplus.core.executor.MybatisCachingExecutor.query(MybatisCachingExecutor.java:92)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)
at com.sun.proxy.$Proxy81.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:173)
上面信息种我们能看出来是com/syl/cloud_2024/mapper/UserMapper.java这个Mapper的sql【SELECT id,name,mobile,location FROM user】查询时因为数据量过大导致了OOM,虽然看不出来具体是哪个方法出了问题,但其实已经有具体sql了,我们也可以定位到具体位置了。
可能会有朋友说,那不行,我这个Mapper下的sql特别多,我不想一个个看,我就想明确的知道问题出在了哪里我才能去解决。此时我们就要借助来排查了,我这里用的工具用的是:【Memory Analyzer Tool 】简【MAT】 。具体的安装方法请参考:。。。。。。。。。
报错信息里面有这么一句话:Dumping heap to /app/java_jar/to/dump/java_pid23650.hprof ...
他将OOM的Heap Dump文件java_pid23650.hprof保存到了/app/java_jar/to/dump目录下,我们把
java_pid23650.hprof文件pull到本地中
[root@shenyulong dump]# pwd
/app/java_jar/to/dump
[root@shenyulong dump]# ll
总用量 35648
-rw-------. 1 root root 36503227 9月 13 20:48 java_pid23650.hprof
通过scp命令将Dump文件pull到本地。
scp root@192.*.*.*:/app/java_jar/to/dump/java_pid4547.hprof ./
打开MAT工具,导入Dump文件。
如上图:
MAT提供了多种视图和工具来帮助分析内存使用情况,包括:
- Leak Suspects:自动分析并列出可能的内存泄漏原因。这是快速定位问题的关键工具。
- Histogram:列出内存中的对象、对象的个数以及大小。这有助于识别哪些类型的对象占用了大量内存。
- Dominator Tree:显示哪个线程以及线程下的哪些对象占用了大量空间。这有助于追踪内存占用的源头。
Thread Overview
:是一个重要的视图,它提供了关于Java应用程序中线程及其内存使用的详细信息
接下来提供了两种分析方法:
方法一:使用Leak Suspects自动分析。
如上图:他帮我分析出来可能有4处地方可能会导致内存泄漏,接下来我们一个个看看。
疑点1:提示说由<系统类加载器>加载的java.lang.Class的6,092个实例占用3,156,760(20.17%)字节。但是我并没有找到相关我们自己的包的一些东西。看不出来问题在哪,我们接着往下看。
虽然这个信息有点多,但是里面有一句话特别重要,我帮大家标红了。【The stacktrace of this Thread is available. See stacktrace. See stacktrace with involved local variables.】他的意思是点击这里能看到 这个线程的堆栈 信息,我们点See stacktrace进去看看。
如上图:在这里找到了我们自己的包信息,我们看下UserServiceImpl类的23行发生了什么。
如上图:果然在23行找到了这个罪魁祸首,这里查了全表数据,数据大小在45M左右,而我启动服务时候jvm分配的最大内存是20M,当然会内存溢出。
继续往下看疑点3:
如上图:大致意思是java.lang的15,092个实例。字符串,由<系统类加载器>加载,占用1,785,448(11.41%)字节。 并没有提供对定位代码有什么有用的信息,这里我们忽略,继续看疑点4。
如上图:同样我们看到了一句话The stacktrace of this Thread is available. See stacktrace.【这个线程的堆栈跟踪是可用的】,我们点进去看看。
如上图:并没有发现有我们自己的包的信息,所以这里我们也先忽略。
到此处我们根据MAT自动分析的【疑点2】里面已经找到了导致OOM的罪魁祸首。
方法二:通过Histogram、Dominator Tree、Thread Overview分析大对象。
解释:
1、Class Name:类名。
2、Objects:Objects 表示当前分析的类或对象实例的数量。这是内存快照中实际存在的对象实例个数。查看某个类的实例数量,可以帮助识别内存中哪些类型的对象占用较多,可能会有助于发现异常增长的对象集合。
3、Shallow Heap:Shallow Heap 是对象自身所占用的内存大小,不包括其引用的其他对象。它反映了一个对象的直接内存消耗。用来衡量单个对象类型对整体内存使用的直接影响。可以帮助识别哪些对象自身占用大量内存。
4、Retained Heap:Retained Heap 是指从 GC 的角度看,如果当前对象被垃圾回收,所能释放的总内存大小。用于识别哪些对象或对象图对内存的总体影响最大。特别是在内存泄漏分析时,通过查看 Retained Heap 可以找到哪些对象的引用链导致了大量内存无法被回收。
如上图,我们点击【Histogram】柱状图后发现除了char[]和String以外有一个ConcurrentHashMap是占内存最大的,这就有点奇怪(其实是因为:ConcurrentHashMap 来缓存从数据库查询得到的数据或其索引导致的),但是这里我在这里并没有定位到具体的代码位置,我们继续通过【Dominator Tree】进一步分析。
解释:
org.springframework.transaction.annotation.AnnotationTransactionAttributeSource:这个对象是支配树的根节点,表明它是主要的内存占用者之一。
Shallow Heap:这个对象本身的内存占用是 32 字节。
Retained Heap:总计 1,800,896 字节,这意味着这个对象及其支配的对象总共占用了约 1.8MB 的内存。
Percentage:显示该对象占用了总内存的 11.51%。
如上图:展开后并没有找到我们包的相关信息。
我们右击展开选择 Path to GC Roots -> exclude all phantom/weak/soft etc.references
如上图:展开后并没有发现有我们自己的包信息,继续通过【Thread Overview】来分析。
Object / Stack Frame:对象信息列。
Name: 线程的名称。在这个例子中,显示的线程名称是
http-nio-8080-exec-1
,这通常表示是Tomcat服务器的一个处理HTTP请求的线程。Shallow Heap: 线程的浅堆内存大小。浅堆指的是对象本身占用的内存大小,不包括它所引用的其他对象。这个值以字节为单位。
Retained Heap: 线程的保留堆内存大小。保留堆指的是由于该线程或其所持有的对象存在而导致JVM无法回收的内存大小。这包括对象本身及其引用的所有对象所占用的内存。
Max. Locals Retained Heap: 最大局部变量所保留的堆内存大小。
Context Class Loader: 线程的上下文类加载器。类加载器用于在Java中加载类,而上下文类加载器是线程用于加载资源和类的类加载器。
Is Daemon: 指示线程是否是守护线程(Daemon thread)。守护线程是那些为其他线程提供服务的线程,当JVM中只剩下守护线程时,JVM会自动退出。
Priority: 线程的优先级。Java中的线程优先级是一个介于1(最低)和10(最高)之间的整数,但它并不保证线程将按照这种优先级顺序执行。
State: 线程的状态。线程可以有多种状态,如
NEW
(新建)、RUNNABLE
(可运行)、BLOCKED
(阻塞)、WAITING
(等待)、TIMED_WAITING
(定时等待)和TERMINATED
(终止)等。State value: 这可能是线程状态的某种数值表示或编码,但它并不是Java标准线程信息中的一部分,可能是特定工具或框架的扩展。
如上图:我们发现org.apache.tomcat.util.threads.TaskThread这个线程类的Retained Heap最大。我们展开后看看。
如上图:展开后找到了罪魁祸手。解决方案:要么增加堆内存,要么使用分页查询。
MAT 排查方式:
• Overview 页面:首先查看内存使用的概览,找出最大的对象类型。
• Dominators Tree:查看内存中占用最大的对象或对象树,找到保留最多内存的根对象。
• Leak Suspects Report:使用 MAT 的自动报告功能,帮助识别可能的内存泄漏点。
• Histogram:查看哪些对象占用了最多的堆空间,关注可疑的大型集合(如 ArrayList, HashMap)。
• Path to GC Roots:追踪从 GC 根节点到大对象的引用路径,识别导致对象无法被回收的引用链。
请求接口:【/OOM/test3】异常日志:
九月 17, 2024 7:02:31 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-09-17 19:02:32.099 [http-nio-8080-exec-1] INFO com.syl.cloud_2024.controller.MemoryOverflowController - 开始请求模拟:【Direct Buffer Memory OOM】
九月 17, 2024 7:02:32 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory] with root cause
java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:695)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at com.syl.cloud_2024.controller.MemoryOverflowController.test3(MemoryOverflowController.java:59)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
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:1071)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
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:696)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
如上日志信息:我们可以直接定位到问题出在了MemoryOverflowController.java的59行,我们来看下这一行发生的什么。
结论:
直接内存不足时,通常会有调用堆外内存分配的代码堆栈信息,可以看到具体的分配代码行。
MAT 排查方式:
• DirectByteBuffer:查找 DirectByteBuffer 对象及其引用路径,分析是否有过多的直接缓冲区对象。
• Histogram:查看与直接内存分配相关的类(如 java.nio.DirectByteBuffer)。
• GC Roots:检查直接内存分配的生命周期和引用路径。
请求接口:【/OOM/test4】异常日志:
2024-09-17 19:20:00.461 [http-nio-8080-exec-2] INFO com.syl.cloud_2024.controller.MemoryOverflowController - 开始请求模拟:【GC Overhead Limit Exceeded】
九月 17, 2024 7:20:01 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded] with root cause
java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:401)
at java.lang.String.valueOf(String.java:3099)
at com.syl.cloud_2024.controller.MemoryOverflowController.test4(MemoryOverflowController.java:77)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
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:1071)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
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:696)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
结论:
GC Overhead 限制超过的错误通常是由于频繁的小对象创建或大对象分配导致的堆空间紧张,通常有堆栈跟踪信息。
原因:
GC 无法及时回收足够的内存,导致 CPU 过多时间花费在 GC 上。
MAT 排查方式:
• Heap Usage:查看整体内存使用模式,看是否存在大量短生命周期的对象或频繁创建的临时对象。
• Dominators Tree:查看是否有单一大对象或大集合占用了大部分内存。
• Histogram:分析频繁出现的对象类型,看是否可以通过优化代码减少这些对象的创建。
请求接口:【/OOM/test5】异常日志:
月 17, 2024 7:42:59 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-09-17 19:42:59.841 [http-nio-8080-exec-1] INFO com.syl.cloud_2024.controller.MemoryOverflowController - 开始请求模拟:【Unable to Create New Native Thread】
九月 17, 2024 7:43:01 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: unable to create new native thread] with root cause
java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at com.syl.cloud_2024.controller.MemoryOverflowController.test5(MemoryOverflowController.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
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:1071)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
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:696)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
结论:
通过日志我们也定位到了具体位置。
原因:
本地代码或库(如 JNI 调用)消耗了过多的本地内存。
MAT 排查方式:
• MAT 无法直接分析本地内存,但可以间接通过查看引用与本地内存关联的对象。
• 使用 Native Memory Tracking (NMT) 辅助 MAT 分析,以了解本地内存分配的情况。
优化建议:
• 减少线程创建:优化代码逻辑,减少不必要的线程创建。使用线程池来管理线程,确保线程的复用。
• 合理设置线程池大小:根据应用程序的负载和硬件资源,合理调整线程池的大小和回收策略。
• 减小每个线程的栈大小:通过 JVM 参数 -Xss 设置较小的线程栈大小。
值此中秋佳节之际,愿明月寄去我最诚挚的祝福,祝大家中秋快乐,阖家幸福,团团圆圆,幸福安康!