Arthas 之源码简要分析
启动模块(boot)
启动 Arthas
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
模块代码:
GitHub Code by arthas-boot
模块入口
在 arthas-boot 模块的 pom.xml 文件中,找到启动类:
<manifest>
<mainClass>com.taobao.arthas.boot.Bootstrap</mainClass>
</manifest>
com.taobao.arthas.boot.Bootstrap.main()
方法:GitHub Code by Bootstrap.java
源码分析
参数解析
使用了阿里开源的组件 cli,对参数进行了解析。
CLI cli = CLIConfigurator.define(Bootstrap.class);
CommandLine commandLine = cli.parse(Arrays.asList(args));
try {
CLIConfigurator.inject(commandLine, bootstrap);
} catch (Throwable e) {
e.printStackTrace();
System.out.println(usage(cli));
System.exit(1);
}
参数处理
对传入的参数进行处理。
如调整日志级别,设置 RepoMirror 地址,Java 版本,telnet/http 的端口检查。
检查 pid
如果在传入参数中没有 pid,则会调用本地 jps 命令,列出 java 进程(当然会排除本身)
long pid = bootstrap.getPid();
// select pid
if (pid < 0) {
try {
pid = ProcessUtils.select(bootstrap.isVerbose(), telnetPortPid, bootstrap.getSelect());
} catch (InputMismatchException e) {
System.out.println("Please input an integer to select pid.");
System.exit(1);
}
if (pid < 0) {
System.out.println("Please select an available pid.");
System.exit(1);
}
}
GitHub Code by ProcessUtils.java
private static Map<Long, String> listProcessByJps(boolean v) {
Map<Long, String> result = new LinkedHashMap<Long, String>();
String jps = "jps";
File jpsFile = findJps();
if (jpsFile != null) {
jps = jpsFile.getAbsolutePath();
}
AnsiLog.debug("Try use jps to lis java process, jps: " + jps);
String[] command = null;
if (v) {
command = new String[] { jps, "-v", "-l" };
} else {
command = new String[] { jps, "-l" };
}
List<String> lines = ExecutingCommand.runNative(command);
AnsiLog.debug("jps result: " + lines);
long currentPid = Long.parseLong(PidUtils.currentPid());
for (String line : lines) {
String[] strings = line.trim().split("\\s+");
if (strings.length < 1) {
continue;
}
try {
long pid = Long.parseLong(strings[0]);
if (pid == currentPid) {
continue;
}
if (strings.length >= 2 && isJpsProcess(strings[1])) { // skip jps
continue;
}
result.put(pid, line);
} catch (Throwable e) {
// https://github.com/alibaba/arthas/issues/970
// ignore
}
}
return result;
}
启动服务端
进入主逻辑,会在用户目录下建立 .arthas
目录,同时下载 arthas-core
和 arthas-agent
等 lib 文件,然后启动客户端和服务端。
if (telnetPortPid > 0 && pid == telnetPortPid) {
AnsiLog.info("The target process already listen port {}, skip attach.", bootstrap.getTelnetPort());
} else {
//double check telnet port and pid before attach
telnetPortPid = findProcessByTelnetClient(arthasHomeDir.getAbsolutePath(), bootstrap.getTelnetPort());
checkTelnetPortPid(bootstrap, telnetPortPid, pid);
// start arthas-core.jar
List<String> attachArgs = new ArrayList<String>();
attachArgs.add("-jar");
attachArgs.add(new File(arthasHomeDir, "arthas-core.jar").getAbsolutePath());
attachArgs.add("-pid");
attachArgs.add("" + pid);
attachArgs.add("-target-ip");
attachArgs.add(bootstrap.getTargetIp());
attachArgs.add("-telnet-port");
attachArgs.add("" + bootstrap.getTelnetPort());
attachArgs.add("-http-port");
attachArgs.add("" + bootstrap.getHttpPort());
attachArgs.add("-core");
attachArgs.add(new File(arthasHomeDir, "arthas-core.jar").getAbsolutePath());
attachArgs.add("-agent");
attachArgs.add(new File(arthasHomeDir, "arthas-agent.jar").getAbsolutePath());
if (bootstrap.getSessionTimeout() != null) {
attachArgs.add("-session-timeout");
attachArgs.add("" + bootstrap.getSessionTimeout());
}
if (bootstrap.getTunnelServer() != null) {
attachArgs.add("-tunnel-server");
attachArgs.add(bootstrap.getTunnelServer());
}
if (bootstrap.getAgentId() != null) {
attachArgs.add("-agent-id");
attachArgs.add(bootstrap.getAgentId());
}
if (bootstrap.getStatUrl() != null) {
attachArgs.add("-stat-url");
attachArgs.add(bootstrap.getStatUrl());
}
AnsiLog.info("Try to attach process " + pid);
AnsiLog.debug("Start arthas-core.jar args: " + attachArgs);
// 启动服务端
ProcessUtils.startArthasCore(pid, attachArgs);
AnsiLog.info("Attach process {} success.", pid);
}
启动客户端
最后通过反射的方式来启动字符客户端,等待用户输入指令。
URLClassLoader classLoader = new URLClassLoader(
new URL[] { new File(arthasHomeDir, "arthas-client.jar").toURI().toURL() });
Class<?> telnetConsoleClas = classLoader.loadClass("com.taobao.arthas.client.TelnetConsole");
Method mainMethod = telnetConsoleClas.getMethod("main", String[].class);
服务端模块(core)
模块代码:
GitHub Code by arthas-core
模块入口
在 arthas-core 模块的 pom.xml 中,找到启动类:
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.taobao.arthas.core.Arthas</mainClass>
</transformer>
</transformers>
com.taobao.arthas.core.Arthas.main()
方法:GitHub Code by Arthas.java
源码分析
连接进程
使用 VirutalMachine.attach(pid)
来连接进程,同时使用 virtualMachine.loadAgent
加载自定义的 agent.
private void attachAgent(Configure configure) throws Exception {
// 省略部分代码
// 连接进程
virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
// 省略部分代码
// 动态加载Agent
virtualMachine.loadAgent(arthasAgentPath,
configure.getArthasCore() + ";" + configure.toString());
}
JavaAgent 代理(agent)
模块代码:
GitHub Code by arthas-agent
模块入口
在 arthas-core 模块的 pom.xml 中,找到启动类:
<manifestEntries>
<Premain-Class>com.taobao.arthas.agent334.AgentBootstrap</Premain-Class>
<Agent-Class>com.taobao.arthas.agent334.AgentBootstrap</Agent-Class>
</manifestEntries>
com.taobao.arthas.agent334.AgentBootstrap.main()
方法:GitHub Code by AgentBootstrap.java
源码分析
main()
方法中对于 arthas-spy
(简单理解为勾子类,类似于 spring aop 的前置方法,后置方法) 进行了加载。
final ClassLoader agentLoader = getClassLoader(inst, arthasCoreJarFile);
将 spyJar 添加到了 BootstrapClassLoader (启动类加载器),优先加载启动类加载器,spy 可以在各个 ClassLoader 中使用。
private static ClassLoader getClassLoader(Instrumentation inst, File arthasCoreJarFile) throws Throwable {
// 构造自定义的类加载器,尽量减少Arthas对现有工程的侵蚀
return loadOrDefineClassLoader(arthasCoreJarFile);
}
异步调用 bind () 方法,启动服务端,监听端口,和客户端进行通讯。
Thread bindingThread = new Thread() {
@Override
public void run() {
try {
bind(inst, agentLoader, agentArgs);
} catch (Throwable throwable) {
throwable.printStackTrace(ps);
}
}
};