JVM实战:调优案例分析_老年代

每秒10w qps 的社交app如何优化gc性能提高3倍

每次Young GC的时候,实际上还是有很多请求没处理完毕,因为每秒请求量太多了,触发Young GC的时候,很多请求是还没处理完毕的。

这就导致Eden区中每次触发Young GC的时候,有很多对象存活下来,导致Survivor区放不下,然后放入老年代

对象快速进入老年代,频繁触发Full GC

// 执行完 Full GC 后对内存空间进行压缩整理,减少内存碎片,不过会造成停顿时间变长
-XX:+UseCMSCompactAtFullCollection

// 执行5次Full GC 后对内存空间进行压缩整理
-XX:CMSFullGCsBeforeCompaction=5

如何优化?

  1. 判断每次Young GC后存活对象有多少,然后就是增加Survivor区的内存,避免对象快速进入老年代
  2. -XX:CMSFullGCsBeforeCompaction=0,每次Full GC后都整理一下内存碎片

第一次Full GC是一个小时,第二次是40分钟

电商大促,严重Full GC导致系统直接卡死

JVM每秒执行一次Full GC,每次耗时几百毫秒

为什么每秒都有一次Full GC?

JVM各个内存区域的使用量,基本没有什么问题,年轻代对象增长并不快,老年代占用了不到10%的空间,元空间也只使用了20%左右的空间

在系统中写了如下代码

System.gc();

如何解决?

  1. 不要使用System.gc()随随变变触发GC
  2. JVM参数中增加如下参数-XX:+DisableExplicitGC,禁止显式执行GC,这样System.gc()就不会生效

线上大促导致内存泄漏和Full GC优化

频繁Full GC三个可能性

  1. 内存分配不合理,导致对象频繁进入老年代,进而引发频繁的Full GC
  2. 存在内存泄漏问题,老年代里驻留了大量的对象,导致稍微有一些对象进入老年代就会触发Full GC
  3. 元空间里的类太多,触发了Full GC

在系统里做了一个JVM本地缓存,很多数据都加载到内存中缓存起来,并且没有使用LRU之类的算法定期淘汰一些缓存中的数据,导致缓存在内存里的对象越来越多,造成了内存泄漏

如何解决?

使用EhCache之类的缓存矿建,设置最多缓存多少个对象,并淘汰一些不常用的数据

每秒仅仅上百请求的系统为什么会因为OOM而奔溃?

因为小编在生产环境中遇到JVM相关的问题比较少,因此将《从零开始带你成为JVM实战高手》这个专栏里面提到的实战案例,总结一下,这样后续遇到相关的问题就能有个具体的排查思路

JVM实战:调优案例分析_tomcat_02

Exception in thread "http-nio-8080-exec-1089" java.lang.OutOfMemoryError:

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 参数

Jetty服务其的NIO机制如何导致堆外内存泄漏?

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堆内存里的,但是在创建这个对象的同时,会在堆外内存中划出一块内存空间和这个对象关联起来

JVM实战:调优案例分析_tomcat_03


为什么会出现堆外内存溢出?

当创建了很多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()生效