值得阅读的内存泄露分析总结和Tomcat调优
这个时候灵光一闪突然回想起我们的tomcat的JVM配置参数里面好像有一个是忽略代码里面调用system.gc()的配置参数。因为这个tomcat容器是被其他单位下发的已经做过优化的,所以里面有一堆的配置参数。其中被我标绿的这个-XX:DisableExplicitGC的作用就是不响应代码里面手动调用system.gc(),看下图: 上面我们可以看到一堆的配置,里面配置含义我们后面再讲。这个时候我以为我已经找到了问题的本质,由于direct Memory在堆外,所以对young gen 的gc过程中是不会回收的。JVM只会在old gen GC(full GC/major GC或者concurrent GC都算)的时候才会对old gen中的对象做reference processing,而在young GC时只会对young gen里的对象做reference processing。也就是说,做full GC的话会对old gen做reference processing,进而能触发Cleaner对已死的DirectByteBuffer对象做清理工作。而如果很长一段时间里没做过GC或者只做了young GC的话则不会在old gen触发Cleaner的工作,那么就可能让本来已经死了的、但已经晋升到old gen的DirectByteBuffer关联的direct Memory得不到及时释放,这么分析看来这里就是问题的根本了。。。 那我们直接把那个参数删掉不就好了吗,我想了想自己的代码里面也没写过system.gc所以应该影响不大,然后就果断去掉了那个参数顺手也设置了个直接内存大小,配置参数是-XX:MaxDirectMemorySize。这个直接内存不设置时,默认大小是最大堆大小,看下图源码。 这么修改完之后又开始做压力测试,这次看到内存很稳定,测试二十多个小时,内存基本增长到两个多G就没再涨了,心情相当开心。然后我再去检查GC日志发现果然出现了很多system.gc的日志,这个之前都是没见过的,看下图: 本以为这次分析到这就结束了,可是没想到后面还有新发现,把我本以为下了定论的答案又推翻了。。。有点自己打自己脸的感觉♂️ Part5.Tomca配置和调优 在分析GC日志的时候,我看到压力测试中GC日志中Full gc的次数有点频繁,而且这种Full gc是Stop-the-world的,很影响应用的响应时间,从GC日志中可以看到基本一次Full GC耗时要一秒多,频率高的话很影响性能。然后我又开始搜资料,搜资料的过程中发现Tomcat8默认用NIO是指在linux服务器下,而我是在Windows的服务器上跑的。。。然后我发现Tomcat支持三种接收请求的模式,分别是:BIO,NIO,APR,其中NIO就是我们上面提到的在linux服务器上默认的模式。网上有人对这三种模式分别作了性能测试,发现APR模式是三种模式里面性能最好的,这种方式是从操作系统级别解决异步IO问题,也是Tomcat运行高并发应用的首选。但是开启比较麻烦,需要一些额外的jar包,有兴趣的也可以自行查资料了解一下。我上面也提到了我这里用的Tomcat是经过优化的,然后我打开server.xml看了一眼,结果两眼一黑,我用的Tomcat疑似采用的就是APR这种模式,因为我看到Server.xml中包含这么一句配置: 然后为了确定我这个猜测我又去看了启动日志,在启动日志中看到了: 这不赤裸裸的告诉我们开启了APR的模式嘛。。。这就意味着上面根据Tomcat8默认NIO模式用到了直接内存,得出的关于我的应用部署的Tomcat为什么占用内存持续上升的结论是不成立的!虽然Tomcat确实有NIO的模式,NIO也确实会用到直接内存,分配直接内存时确实会手动调用system.gc(),然后tomcat里面配置-XX:DisableExplicitGC确实会影响内存分配导致直接内存堆积,可是和我这并没什么关系啊。。此时心里万马奔腾,但我冷静一想当我去掉-XX:DisableExplicitGC时,GC日志里面出现了很多Full GC的日志,那不是因为分配直接内存引起的还有谁再调用呢。没办法,只能一直手动抓线程栈来分析,用下面的指令就可以把当前线程栈输出到一个txt文档中。
这个pid就是你的java线程id。果然抓了几次就被我抓到了现场,线程栈中果然有个线程在执行gc操作,看下图: 这次感觉自己应该是找到了问题本质了,光看这个也看不出什么然后上网搜了下,发现也是Tomcat配置引起的,真相只有一个就是下面这个配置: (编辑:西安站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |