请求延迟导致内存溢出
《从零开始带你成为JVM实战高手》
Exception in thread "http-nio-8080-exec-1089" java.lang.OutOfMemoryError: Java heap space
http-nio-8080-exec-1089说的就是tomcat的工作线程
发现占据内存最大的就是大量的byte[]数组,一大堆byte[]数组占据了大约8g左右的内存空间,给Tomcat应用分配的堆内存也就是8G左右
这些数组被哪个类引用?
org.apache.tomcat.util.threads.TaskThread
byte[10008192] @ 0x7aa800000 GET /order/v2 HTTP/1.0-forward...
byte[10008192] @ 0x7aa800000 GET /order/v2 HTTP/1.0-forward...
byte[10008192] @ 0x7aa800000 GET /order/v2 HTTP/1.0-forward...
byte[10008192] @ 0x7aa800000 GET /order/v2 HTTP/1.0-forward...
MAT中可以查看具体有哪些内存存在,Tomcat的工作线程大致有400个左右,每个Tomcat线程会创建2个byte[]数组,每个byte[]数组是10MB左右
400个工作线程同时在处理请求,结果创建出来了8G内存的byte[]数组,进而导致内存溢出。
系统每秒的qps是100,并不是400。
每个请求处理完毕需要4s时间
为什么Tomcat工作线程在处理一个请求时会创建2个10MB的数组
max-http-header-size: 10000000
为什么一个请求需要4s?
Timeout Exception....
通过rpc调用时出现了大量的请求超时,超时时间是4s
超时时间设为1s。每秒100个请求过来,只有200个数组,占据2g内存,远不会把内存塞满,然后1秒内这100个请求全部超时,请求就处理结束了
适当调小 max-http-header-size 参数
堆外内存溢出
nio handle failed java.lang.OutOfMemoryError: Direct buffer memory
at org.eclipse.jetty.io.nio.xxxx
at org.eclipse.jetty.io.nio.xxxx
at org.eclipse.jetty.io.nio.xxxx
堆外内存如何申请和释放?
在Java代码中使用堆外内存,是使用DirectByteBuffer类,这个类本身是在JVM堆内存里的,但是在创建这个对象的同时,会在堆外内存中划出一块内存空间和这个对象关联起来 为什么会出现堆外内存溢出?
当创建了很多DirectByteBuffer对象,占用了大量的堆外内存,这些DirectByteBuffer对象没有GC线程来回收,就不会释放堆外内存。当堆外内存被大量的DirectByteBuffer对象关联使用了,当没有堆外内存还继续申请堆外内存时,就会报内存溢出
内存分配不合理,给了年轻代100mb的空间,老年代反而给了700mb的空间,进而导致Survivor只有10MB左右的空间
在 young gc 过后,一些存活下来的对象(包括一些DirectByteBuffer在内)会超过10mb,没法放入Survivor中,直接进入老年代。这样老年代中的DirectByteBuffer会越来越多,其实这些老年代中的DirectByteBuffer很多都是可以回收状态,但是因为老年代一直没塞满,没有触发full gc,自然就不会回收老年代里的DirectByteBuffer,导致堆外内存一直无法回收,最终导致堆外内存溢出
Java NIO没有考虑过这个问题吗?
其实这种问题Java NIO是考虑到的,它每次分配新的堆外内存的时候,都会调用System.gc()去提醒JVM主动执行gc去回收掉没人引用的DirectByteBuffer对象,释放堆外内存空间。
但是我们在JVM中设置了如下参数
-XX:+DisableExplicitGC
导致System.gc()不生效
解决方案:合理分配内存,让年轻代有更多内存,将参数该为-XX:-DisableExplicitGC,让System.gc()生效
|