在实际的java应用里面我们经常会遇到OOM的场景,所以遇到这种问题我们就需要进行排查,今天这篇文章我们主要介绍的jvm内存溢出异常之GC overhead limit exceeded。
这个异常其实并不陌生,遇到这个异常的情况下,大概率是堆内存溢出了。首先我们看看完整的异常错误信息:
java.lang.OutOfMemoryError: GC overhead limit exceeded at com.oom.demo.OomDemo.HelloController.testHeapOOM(HelloController.java:46) 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:221) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:775) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217)
这是实践中抛出的异常信息。下面我们完整的演示一下。
一、模拟异常
要解决GC overhead limit exceeded这种异常,我们首先就来模拟生成一个这个异常的代码(在实际工作中造成这个异常的情况非常多根据实际情况来操作即可),首先我们编写一个方法:
@RequestMapping("/testHeapOOM") public String testHeapOOM(int num) { List<HeapOOM> list = new ArrayList<>(); for (int i = 0; i < num; i++) { list.add(new HeapOOM()); } return "success"; }
这里的逻辑是通过动态传值来new对象,所以测试的时候,然后我们启动下项目,启动的时候配置下堆内存如下:
-Xmx50m -Xms50m
我们请求地址如下:http://localhost:8080/testHeapOOM?num=10000000
当请求之后,我们就可以看到上面的异常了。
二、解决内存溢出:GC overhead limit exceeded
这里我们可以根据上面抛出的异常找到代码所在地是
list.add(new HeapOOM());
这一行,但是这一行其实是反应不出来我们遇到的问题的,所以我们需要查看这里涉及的逻辑:
for (int i = 0; i < num; i++) { list.add(new HeapOOM()); }
找到这里我们就能知道这个对象是可以无限创建的,那么创建的时候就会占用内存。因此我们可以肯定这里是不合理的。所以找到了问题点,我们就知道怎么解决了,解决方案是:
1、把这里的for循环修改为合理的创建 2、根据业务进行排查,是否需要创建这么多对象
三、补充说明
对于上面来说,直接打印了日志的具体异常信息,所以我们可以根据异常信息直接定位到出问题的地方,但是如果我们没有异常日志怎么办?例如jar包运行在docker上的,docker没有外映射日志,docker直接自动销毁了,我们就看不到日志了,此时我们可以做如下措施:
一、把jar相关的日志映射到docker磁盘上。
二、添加如下配置,把jvm的日志打印出来:
-XX:HeapDumpPath=D:\jvmlog\xxx.hprof
此时启动之后,报错了就会在目录下生成一个日志文件
对于这个日志我们如何查看呢?可以参考这篇文章《java项目配置-XX:HeapDumpPath生成的xxx.hprof文件如何打开》我们可以在当前出错的栈上找到对应的代码:
四、总结
对于出现GC overhead limit exceeded这种内存溢出的时候,本文我们介绍了一下排查方法,及可能出现的原因,总结如下:
1、通过日志进行排查,如果没有日志,可以添加
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\jvmlog\xxx.hprof
这里来配置纯内存溢出的日志。
2、出现这个GC overhead limit exceeded主要是堆内存大小不够了,首先排查下业务是否必须要这么编码,是否可以改变编码的逻辑方式,如果编码的逻辑方式不能再修改了,则需要增大jvm配置的堆内存大小,对应的参数主要是:
-Xmx100m -Xms100m
还没有评论,来说两句吧...