【Arthas 专题篇.二 - 源码解析】Arthas boot的启动流程

本文将详细剖析Arthas的启动流程,从arthas-boot.jar的入口类开始,讲解如何调用arthas-core.jar、arthas-agent.jar和arthas-spy.jar,以及在启动过程中遇到的服务端口占用问题。通过源码分析,揭示Arthas启动的核心步骤。
摘要由CSDN通过智能技术生成

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]) });
	

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霸道产品爱上我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值