每次导出,导出的内存利用率都会小幅或大幅增加。一次VIP导出后,导出的内存利用率会较大增加。php
十次较小导出的结果,从 15:30 有一个小步的内存利用率攀升。
html
一次VIP大流量导出的结果,从 14:04 有一个大幅的陡峭的攀升。
数据库
从网上查资料知,可使用 MAT 工具来排查此类问题。排查基本步骤以下:缓存
STEP1: 运行一次比较大的导出后,使用 jmap 工具从服务器生成内存文件 mem.bin。使用 top -c M 拿到占用内存最高的 pid;而后服务器
sudo su app jmap -dump:live,format=b,file=/tmp/mem.bin pid chmod 777 /tmp/mem.bin
STEP2: 将 mem.bin scp 到本地app
scp shuqin@IP:/tmp/mem.bin /tmp/mem.bin scp shuqin@IP:/tmp/mem.bin /tmp/mem.bin
STEP3: 在 http://www.eclipse.org/mat/downloads.php 下载 MAT 工具,解压安装便可。eclipse
STEP4: 在 MAT 工具打开 mem.bin , 分析内存文件,定位缘由和修复问题。
工具
Overview: 内存全貌,找到最大内存占用对象;
优化
点击TopConsumers, 概览全部占用内存比较较大的对象集合。
.net
点击 DominatorTree (支配树) ,能够看到全部占用内存比较多的对象的引用链
点击 Leak Suspects ,查看内存泄露的最大嫌疑。能够看到 export-execute-thread1 占用了一个大对象列表 List
线程栈引用
为何这些对象依然被导出任务线程持有? 缘由极可能是:导出线程是 Context 持有者,Context 在导出结束时没有清理, 而线程由于某种缘由没有退出和被回收,致使 Context 依然在内存里。 作了个 Context 清理以后,再部署并 dump 文件,发现以前的List
清理 Context 以后,从新运行发现内存占用依然逐增。 从新 dump, 发现貌似 groovy 脚本致使。缘由多是: 在每次导出前,都会把字段配置的groovy脚本加载到针对每次导出的单线程中,而导出结束时这些脚本没有清理。再作一层脚本引用的清理:
fieldConfig.getCacheScript().setBinding(null); fieldConfig.setCacheScript(null);
发现再也不有 Groovy 脚本占用内存的嫌疑。
第二轮优化后,虽然 groovy 的嫌疑有所减轻,可仍是没有彻底消除。这下,从代码上确实看不出什么了。
原来的策略是每次导出,都从数据库里克隆一份报表字段配置,不管是否用到,都建立针对每一个字段的缓存的Script对象,导出结束时清除(可能没真正清除掉);如今的策略是,作一个Script对象的缓存池。若是没有Script对象的执行,那么也不会建立多余的Script对象占用空间。从30天内存占用率图来看,在最后一次发布后,尽管有屡次大流量导出,内存占用保持平稳。
10 Tips for using the Eclipse Memory Analyzer
MAT - Memory Analyzer Tool 使用进阶