今天继续整理MFT内存持续增长的内存问题的渐进分析过程。
首先我们还是回顾下前面查找后的一些关键结论性的内容,即已经发现了是不断的读取Control临时目录和临时文件,而且持续增长导致了JVM内存无法释放,当前策略就是对临时目录进行删除,但是删除的进度很慢。而我们需要进一步搞清楚的就是为何会启动Thread-0对临时目录进行扫描和读取。
前期涉及到的一些知识点和知识内容总结
对于这个问题的分析,个人感觉进展比较缓慢的一个原因还是对于一些技术知识点的积累明显还是存在不足,导致很多时候基本都是在边分析问题,边进行关键知识的学习,具体涉及到的知识点包括:
1. JVM的内存模型和启动参数设置
2. Linux下常用的一些监控命令,jstack, jmap, 对内存gc数据的含义理解
3. 对jmap dump内存数据的MAT工具分析,具体的分析逻辑,堆栈日志的分析方法,死锁分析
4. 对Oracle MFT本身产品架构,底层数据库表,作业和调度运行的含义的理解
5. 对Oracle ESS调度产品的一些理解,特别是Ess的Schedule和Job的运行机制
6. 对只能拿到Jar包的时候,通过反编译工具进行反编译后代码的分析方法
7. 对多线程,线程模型,线程运行机制的一些理解
8. 对Oralce Job ,Oracle Schedule Job两类调度作业的一些理解
9. 对类似OneAPM等APM性能监控分析软件的了解,以及插件对已经应用的运行影响分析
前期已经分析的一些关键点总结
为了查询这个问题,我们主要查了几个关键的地方
1. 对于Oracle Job的定义配置,发现有定时刷新物化视图的Job,初步分析不影响。
2. 发现了数据库Schedule类的Job
dba_scheduler_job_run_details ,dba_scheduler_jobs 但是从表里难以看到有价值的内容。
3. 发现了MFT本身的临时文件Purge清扫的Schedule调度任务,但是当前只有默认,而且Inactive。
因此这个使我们的分析基本陷入了停滞,能够进一步分析的只能够是对MFT相关涉及到调度作业的Jar包进行反编译后源代码分析,看能否找到有价值的内容。
基于前面几篇文章提到的内容,我们进一步找到涉及到MFT调度相关的两个关键Jar包
core.jar 和 ess-client.jar
而后续的分析我们主要是基于对这两个Jar的反编译后的源代码进行分析。而里面几个比较重要的包主要体现在engine, purge, scheduler, ess, mds, system这几个关键的包。具体的关键类和方法的实现也主要体现在这几个包里面。
下面我主要记录下一些关键的分析和追溯过程。
基于Purge关键字的一些搜索
通过对Purge关键字的搜索,我们追溯到MFT后台的几张和Purge清扫任务相关的表,即:
MFT_PURGE_MESSAGE_INFO + batchId;
MFT_PURGE_PAYLOAD_INFO_+ batchId;
MFT_PURGE_MSG_EVT_INFO_ + batchId;
可以看到实际清扫任务的执行和清扫范围往往需要从这几个表里面获取数据并执行清扫操作,在执行完Purge清扫操作后会更加数据表里面行的状态信息。但是我们查询了下测试环境的数据库表,里面还是发现了大量的Purge_Status状态是Error的数据,而这些数据本身应该也会发起进一步的重试清除。
但是我们进一步发现payload_reference字段的内容都是具体的某一个文件,感觉和我们追溯的对Control目录的临时文件清扫还不一样。
基于目录和文件扫描的进一步搜索
我们看到的是Thread-0会对目录进行扫描,并且列出目录里面的所有文件,在我们的Dump内存文件分析也可以看到实际的堆栈记录是在列出文件或者是对File文件进行实例化的时候出现错误。
因此我们首先就是考虑通过FileList关键字去搜索,但是一开始并没有搜索到内容,后续我们找到一个重要的关键字就是fileList,基于这个关键字我们基于追溯到一些关键的内容。
purge入口方法
-> purgeBatchOfPayloads方法
->进行了目录扫描和删除文件操作
purgeNow入口方法
->new ThreadWorkExecutor("startPurge"..) 新线程启动
-->RuntimePurgeService.getInstance().purgeInternal(..)方法
通过purgeInternal,追溯到一个新的Schedule入口
onScheduleExpiry(..)方法
->executePurgeSchedule(..) 方法
->RuntimePurgeService.getInstance().purgeInternal(..)方法
到这里后基本可以理清楚的一个问题就是Purge操作本身有两种触发方式,一种是基于Purge Schedule调度计划定时触发,一种则是直接实时的Purge触发。这个和MFT管控界面前台的Purge操作的两种类型模式是完全匹配的。到了这里有一个新的发现就是onScheduleExpiry方法。
但是在我们继续搜索onScheduleExpiry这个关键字的时候并没有找到这个方法的入口点在哪里?或者说什么时候会触发这个方法运行。而我们从字面上去理解的话,可以理解为在调度过期的时候就会触发这个方法的执行,如果是这样的话就比较好解释为何我们的默认Schedule是Inactive状态的时候也会触发清扫操作。
而再进一步检索
executePurgeSchedule(Map params)
->params中有时间设置
-> transferCriteria.setRetentionPeriod(7);
可以看到对于这个onScheduleExpiry方法的执行,仍然是默认去清理7天前的临时目录和文件,如果是这样的话整体上还是能够解释的通。即我们在将默认调度任务设置在Inactive状态的时候反而是触发了大量的数据和文件清除操作。
那么基于以上思考,实际可以进一步验证的就是激活这个调度,但是可以将调度频率设置长一点,把清扫的默认天数设置大点,看下具体的文件清扫作业情况,比如我们将清扫天数设置为100天,就可以去观察是否还会去大量读入100天内的临时文件和目录。
注1:我们希望从内存dump文件中搜索到onScheduleExpiry这个关键字,但是实际上我们对内存dump文件进行关键字搜索的时候并没有找到匹配信息,但是如果我们搜索startPurge或purgeNow关键字可以搜索到相关的记录信息。因此是否这个点触发仍然是存在疑问。对于在Task任务类里面的onScheduleExpiry是如何触发的暂时还没有完全了解清楚。
注2:在我们对Purge关键字进行搜索和向上追溯的时候,又到了OneAPM插件的一个sample Judge线程上面,具体对应这个线程是干什么的搜索相关资料暂时没有得到结果。
经过今天的分析实际,初步梳理出两个有价值的内容
1. new ThreadWorkExecutor("startPurge"..) 是启动新线程的一个最可能点。
2. 即使调度不激活可能也会触发onScheduleExpiry里面的清扫作业,但是需要进一步验证。
以上是今天的一些关键分析内容,作为记录和归档整理。