背景
在解决了编译问题后, 就可以实时每个方法的调用耗时. 之后面临新的问题:
- 如何准确展示最终的问题耗时方法?
- 如何展示方法间调用与被调用关系?
问题分析
简单的看这两个问题, 1是如何描述一个方法,
那么首先想到的就是thread.getStackTrace().
然而实际直接获取thread.getStackTrace()是有问题的:
- getStackTrace是栈结构的, 只有父子调用关系, 没有方法之间的兄弟关系. 信息缺失.
- getStackTrace是实时数据, 这导致:
- 其只反应了当前时刻的状态, 不能表达方法之间的兄弟关系. 也是信息缺失.
- 方法在出栈时才能计算出方法时长, 有可能是"过去时"(例如try-catch时)此时再拿堆栈, 错过了最佳时刻, 是不准确的.
既然thread.getStackTrace()不可取, 那必须需要考虑其他办法了.
解决方案
确认结构
首先分析我们的目的:
- 记录耗时方法
- 记录父子耗时方法关系,兄弟耗时方法关系
一个方法, 可能被父方法调用, 也可能调用多个子方法. 有上下父子关系, 也要前后兄弟关系.
那么将每个方法看做一个节点, 有一下特点
- 每个节点可能是其他节点的子节点
- 包含一个或者多个子节点
- 也可能没有节点.
比如如下代码:
void method1(){
method2();
method3();
method4();
}
void method2(){
method5();
}
void method3(){
method6();
method7();
}
void method4(){
}
用图形表达后, 应该类似这个样子:

所以我们可以模拟一套树形结构来记录方法堆(就不能说是栈了:)).
数据结构确认后, 实现就容易了. 使用任意的List, 就可以实现:
class MethodNode{
//父方法节点
MethodNode parent;
//子方法节点
List<MethodNode> children;
//当前方法名
String methodName;
}
因此如上代码中的method3(), 用数结构记录的话, 大致是下面这个样子:
(只是个大概, 因为mermaid绘图有些诡异, 所以有不准确的地方)

逻辑实现
确认了数据结构, 接下来考虑如何关联各个方法节点.
目前, 已经在每个方法的起始和结束插入了代码用来计算方法耗时,
那么也可以扩展这些被插入代码, 将各个方法关联起来.
如下伪代码所示. 任何时间, 只要root不为空, 都可以通过root获取当前已经保存的所有方法调用堆栈.
//当前正在运行的方法
static MethodNode currNode;
//根节点方法
static MethodNode root;
void onMethodStart(MethodNode methodNode){
//方法开始, 记录时间
methodNode.startTime = System.currentTimeMillis();
if(currNode != null){
//设置父方法
methodNode.parent = currNode;
//父方法中添加子方法
currNode.children.add(methodNode);
//更新当前方法.
currNode = methodNode;
}
if(root==null){
//设置根方法
root = this;
currNode = this;
}
}
void onMethodEnd(MethodNode methodNode){
//方法结束, 记录时间
methodNode.endTime = System.currentTimeMillis();
//更新当前方法currNode为父方法
currNode = methodNode.parent;
}
优化问题
以上初步实现了方法监控功能的记录.
但是因为实时存储了方法的调用信息, 所以内存激增. 导致内存问题.
如何优化, 待后续...