历史文章:
- OGNL语法规范
- 消失的堆栈
- Arthas原理系列(一):利用JVM的attach机制实现一个极简的watch命令
- Arthas原理系列(二):总体架构和项目入口
- Arthas原理系列(三):服务端启动流程
- Arthas原理系列(四):字节码插装让一切变得有可能
前言
本篇文章主要讲我们在终端中敲入的命令是如何被 arthas 服务器识别并且解释的。要注意这个过程是 arthas 对所有命令执行过程的抽闲个,对于具体命令的执行过程我会在后面的系列文章中再说。
arthas 服务端的启动
在上一篇文章中,我们跟踪了整个 arthas 工程的入口方法:com.taobao.arthas.agent334.AgentBootstrap#main
,在这个方法中,最重要的一个步骤就是启动过了一个绑定线程
private static synchronized void main(String args, final Instrumentation inst) {
try {
// 1. 程序运行前的校验,
// arthas如果已经存在,则直接返回
// 入参中必须要包含arthas core等
// 这些代码细节不会影响我们对主流程的理解,因此暂时删除
final ClassLoader agentLoader = getClassLoader(inst, arthasCoreJarFile);
Thread bindingThread = new Thread() {
@Override
public void run() {
try {
bind(inst, agentLoader, agentArgs);
} catch (Throwable throwable) {
throwable.printStackTrace(ps);
}
}
};
bindingThread.setName("arthas-binding-thread");
bindingThread.start();
bindingThread.join();
} catch (Throwable t) {
t.printStackTrace(ps);
try {
if (ps != System.err) {
ps.close();
}
} catch (Throwable tt) {
// ignore
}
throw new RuntimeException(t);
}
bind
这个线程的运行时会调用com.taobao.arthas.agent334.AgentBootstrap#bind
,这个方法的详细代码如下:
private static void bind(Instrumentation inst, ClassLoader agentLoader, String args) throws Throwable {
/**
* <pre>
* ArthasBootstrap bootstrap = ArthasBootstrap.getInstance(inst);
* </pre>
*/
Class<?> bootstrapClass = agentLoader.loadClass(ARTHAS_BOOTSTRAP);
Object bootstrap = bootstrapClass.getMethod(GET_INSTANCE, Instrumentation.class, String.class).invoke(null, inst, args);
boolean isBind = (Boolean) bootstrapClass.getMethod(IS_BIND).invoke(bootstrap);
if (!isBind) {
String errorMsg = "Arthas server port binding failed! Please check $HOME/logs/arthas/arthas.log for more details.";
ps.println(errorMsg);
throw new RuntimeException(errorMsg);
}
ps.println("Arthas server already bind.");
}
这段方法用反射的方法调用了com.taobao.arthas.core.server.ArthasBootstrap
的静态方法getInstance
,并且把从main
方法中解析到参数再传到这个getInstance
中。
getInstance
从这个名字看就是返回一个ArthasBootstrap
的实例,事实上代码的逻辑也是这样的,其中最关键的就是ArthasBootstrap
的构造函数函数:
private ArthasBootstrap(Instrumentation instrumentation, Map<String, String> args) throws Throwable {
this.instrumentation = instrumentation;
String outputPath = System.getProperty("arthas.output.dir", "arthas-output");
arthasOutputDir = new File(outputPath);
arthasOutputDir.mkdirs();
// 1. initSpy()
// 加载SpyAPI这个类
initSpy(instrumentation);
// 2. ArthasEnvironment
// 初始化arthas运行的环境变量
initArthasEnvironment(args);
// 3. init logger
loggerContext = LogUtil.initLooger(arthasEnvironment);
// 4. init beans
// 初始化结果渲染和历史命令管理的相关类
initBeans();
// 5. start agent server
// 启动server,开始监听
bind(configure);
// 注册一些钩子函数
executorService = Executors.newScheduledThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
final Thread t = new Thread(r, "arthas-command-execute");
t.setDaemon(true);
return t;
}
});
shutdown = new Thread("as-shutdown-hooker") {
@Override
public void run() {
ArthasBootstrap.this.destroy();
}
};
transformerManager = new TransformerManager(instrumentation);
Runtime.getRuntime().addShutdownHook(shutdown);
}
在这个构造函数中,最重要的就是com.taobao.arthas.core.server.ArthasBootstrap#bind
这个方法
private void bind(Configure configure) throws Throwable {
// 无关紧要的一些前置操作,先删除掉
try {
// 关于arthas tunnel server,请参考:
// https://arthas.aliyun.com/doc/tunnel.html
if (configure.getTunnelServer() != null) {
tunnelClient = new TunnelClient();
tunnelClient.setAppName(configure.getAppName());
tunnelClient.setId(configure.getAgentId());
tunnelClient.setTunnelServerUrl(configure.getTunnelServer());
tunnelClient.setVersion(ArthasBanner.version());
ChannelFuture channelFuture = tunnelClient.start();
channelFuture.await(10, TimeUnit.SECONDS);
}
} catch (Throwable t) {
logger().error("start tunnel client error", t);
}
try {
// 将一些非常关键的参数包装成ShellServerOptions对象
ShellServerOptions options = new ShellServerOptions()
.setInstrumentation(instrumentation)
.setPid(PidUtils.currentLongPid())
.setWelcomeMessage(ArthasBanner.