arthas 启动流程
一. arthas 启动
arthas-boot.jar 启动入口类
1.1 启动流程
boot 仅仅是一个调用窗口,真正的核心代码并不在 boot 这个包内。
boot 会调用 “arthas-core.jar”, “arthas-agent.jar”, “arthas-spy.jar” 这三个包的功能,boot仅仅只做了启动检查的操作,也导致了后面服务端口占用的各种问题,无法在boot这边进行解决。
可参考图示,关注代码如何调用core的地方
1.2 main方法
启动类
com.taobao.arthas.boot.Bootstrap
main方法主要逻辑
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException,
ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
// 省略 初始化环境变量 JAVA_HOME,JAVA_OPTIONS等等
......
// 新建一个处理器 内涵 启动命令,启动参数等
Bootstrap bootstrap = new Bootstrap();
// 命令行解析器
CLI cli = CLIConfigurator.define(Bootstrap.class);
// 这里会会读取启动命令 如:java -jar arthas-boot.jar
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);
}
if (bootstrap.isVerbose()) {
AnsiLog.level(Level.ALL);
}
if (bootstrap.isHelp()) {
System.out.println(usage(cli));
System.exit(0);
}
if (bootstrap.getRepoMirror() == null || bootstrap.getRepoMirror().trim().isEmpty()) {
bootstrap.setRepoMirror("center");
if (TimeUnit.MILLISECONDS.toHours(TimeZone.getDefault().getOffset(System.currentTimeMillis())) == 8) {
bootstrap.setRepoMirror("aliyun");
}
}
if (bootstrap.isVersions()) {
System.out.println(UsageRender.render(listVersions()));
System.exit(0);
}
if (JavaVersionUtils.isJava6() || JavaVersionUtils.isJava7()) {
bootstrap.setuseHttp(true);
AnsiLog.debug("Java version is {}, only support http, set useHttp to true.",
JavaVersionUtils.javaVersionStr());
}
// 检查 telnet/http 套接字,是否能够通信
long telnetPortPid = -1;
long httpPortPid = -1;
if (bootstrap.getTelnetPortOrDefault() > 0) {
telnetPortPid = SocketUtils.findTcpListenProcess(bootstrap.getTelnetPortOrDefault());
if (telnetPortPid > 0) {
AnsiLog.info("Process {} already using port {}", telnetPortPid, bootstrap.getTelnetPortOrDefault());
}
}
if (bootstrap.getHttpPortOrDefault() > 0) {
httpPortPid = SocketUtils.findTcpListenProcess(bootstrap.getHttpPortOrDefault());
if (httpPortPid > 0) {
AnsiLog.info("Process {} already using port {}", httpPortPid, bootstrap.getHttpPortOrDefault());
}
}
long pid = bootstrap.getPid();
// 若是没有启动cli没指定pid,会有选择
if (pid < 0) {
try {
// 选择想要监听的java线程PID
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);
}
}
// 检查输入的pid是否存在
checkTelnetPortPid(bootstrap, telnetPortPid, pid);
if (httpPortPid > 0 && pid != httpPortPid) {
AnsiLog.error("Target process {} is not the process using port {}, you will connect to an unexpected process.",
pid, bootstrap.getHttpPortOrDefault());
AnsiLog.error("1. Try to restart arthas-boot, select process {}, shutdown it first with running the 'stop' command.",
httpPortPid);
AnsiLog.error("2. Or try to use different http port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port 9999", httpPortPid);
System.exit(1);
}
// 找到 arthas 目录
File arthasHomeDir = null;
if (bootstrap.getArthasHome() != null) {
verifyArthasHome(bootstrap.getArthasHome());
arthasHomeDir = new File(bootstrap.getArthasHome());
}
if (arthasHomeDir == null && bootstrap.getUseVersion() != null) {
// 获取 libArthasJniLibrary 依赖在 arthas.home/lib里
File specialVersionDir = new File(System.getProperty("user.home"), ".arthas" + File.separator + "lib"
+ File.separator + bootstrap.getUseVersion() + File.separator + "arthas");
if (!specialVersionDir.exists()) {
// try to download arthas from remote server.
DownloadUtils.downArthasPackaging(bootstrap.getRepoMirror(), bootstrap.isuseHttp(),
bootstrap.getUseVersion(), ARTHAS_LIB_DIR.getAbsolutePath());
}
verifyArthasHome(specialVersionDir.getAbsolutePath());
arthasHomeDir = specialVersionDir;
}
// Try set the directory where arthas-boot.jar is located to arhtas home
if (arthasHomeDir == null) {
CodeSource codeSource = Bootstrap.class.getProtectionDomain().getCodeSource();
if (codeSource != null) {
try {
// https://stackoverflow.com/a/17870390
File bootJarPath = new File(codeSource.getLocation().toURI().getSchemeSpecificPart());
// 验证 home "arthas-core.jar", "arthas-agent.jar", "arthas-spy.jar" 是否存在
verifyArthasHome(bootJarPath.getParent());
arthasHomeDir = bootJarPath.getParentFile();
} catch (Throwable e) {
// 异常被吃掉了,没有做处理
}
}
}
// ignore 检查版本 - 需要 boot版本与core一致,后面就是调用core的方式了
// 1.3 建立通信
if (telnetPortPid > 0 && pid == telnetPortPid) {
AnsiLog.info("The target process already listen port {}, skip attach.", bootstrap.getTelnetPortOrDefault());
} else {
//double check telnet port and pid before attach
telnetPortPid = findProcessByTelnetClient(arthasHomeDir.getAbsolutePath(), bootstrap.getTelnetPortOrDefault());
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);
if (bootstrap.getTargetIp() != null) {
attachArgs.add("-target-ip");
attachArgs.add(bootstrap.getTargetIp());
}
if (bootstrap.getTelnetPort() != null) {
attachArgs.add("-telnet-port");
attachArgs.add("" + bootstrap.getTelnetPort());
}
if (bootstrap.getHttpPort() != null) {
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.getAppName() != null) {
attachArgs.add("-app-name");
attachArgs.add(bootstrap.getAppName());
}
if (bootstrap.getUsername() != null) {
attachArgs.add("-username");
attachArgs.add(bootstrap.getUsername());
}
if (bootstrap.getPassword() != null) {
attachArgs.add("-password");
attachArgs.add(bootstrap.getPassword());
}
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());
}
if (bootstrap.getDisabledCommands() != null){
attachArgs.add("-disabled-commands");
attachArgs.add(bootstrap.getDisabledCommands());
}
AnsiLog.info("Try to attach process " + pid);
AnsiLog.debug("Start arthas-core.jar args: " + attachArgs);
// 调用core
ProcessUtils.startArthasCore(pid, attachArgs);
AnsiLog.info("Attach process {} success.", pid);
}
// ignore
......
// 启动telnet client
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);
List<String> telnetArgs = new ArrayList<String>();
if (bootstrap.getCommand() != null) {
telnetArgs.add("-c");
telnetArgs.add(bootstrap.getCommand());
}
if (bootstrap.getBatchFile() != null) {
telnetArgs.add("-f");
telnetArgs.add(bootstrap.getBatchFile());
}
if (bootstrap.getHeight() != null) {
telnetArgs.add("--height");
telnetArgs.add("" + bootstrap.getHeight());
}
if (bootstrap.getWidth() != null) {
telnetArgs.add("--width");
telnetArgs.add("" + bootstrap.getWidth());
}
// telnet port ,ip
telnetArgs.add(bootstrap.getTargetIpOrDefault());
telnetArgs.add("" + bootstrap.getTelnetPortOrDefault());
// fix https://github.com/alibaba/arthas/issues/833
Thread.currentThread().setContextClassLoader(classLoader);
mainMethod.invoke(null, new Object[] { telnetArgs.toArray(new String[0]) });
}