《arthas源码分析》arthas启动流程分析(二)

执行java -jar 之后,便会进入Arthas|#main,下面对其进行详细分析。

Arthas

Arthas#main

创建Arthas。

当发生异常时,打印错误日志,并退出System.exit(-1);
在arthas-boot.jar 进程中会接受到exit code,当exit code = -1时,会结束arthas 进程。

主要做了两件事1、解析参数,2、attach到目标进程。

public static void main(String[] args) {
    try {
        new Arthas(args);
    } catch (Throwable t) {
        AnsiLog.error("Start arthas failed, exception stack trace: ");
        t.printStackTrace();
        System.exit(-1);
    }
}

private Arthas(String[] args) throws Exception {
        //解析
        attachAgent(parse(args));
    }
Arthas#parse

解析启动参数,并封装进Configure

private Configure parse(String[] args) {
    //目标进程id
    Option pid = new TypedOption<Integer>().setType(Integer.class).setShortName("pid").setRequired(true);
    //arthas-core.jar路径
    Option core = new TypedOption<String>().setType(String.class).setShortName("core").setRequired(true);
    //arthas-agent.jar路径
    Option agent = new TypedOption<String>().setType(String.class).setShortName("agent").setRequired(true);
    //目标进程的ip
    Option target = new TypedOption<String>().setType(String.class).setShortName("target-ip");
    //telnet port
    Option telnetPort = new TypedOption<Integer>().setType(Integer.class)
            .setShortName("telnet-port").setDefaultValue(DEFAULT_TELNET_PORT);
    //http port
    Option httpPort = new TypedOption<Integer>().setType(Integer.class)
            .setShortName("http-port").setDefaultValue(DEFAULT_HTTP_PORT);
    //超时时间
    Option sessionTimeout = new TypedOption<Integer>().setType(Integer.class)
                    .setShortName("session-timeout").setDefaultValue("" + Configure.DEFAULT_SESSION_TIMEOUT_SECONDS);
    //tunnel-server.jar路径
    Option tunnelServer = new TypedOption<String>().setType(String.class).setShortName("tunnel-server");
    //agent id
    Option agentId = new TypedOption<String>().setType(String.class).setShortName("agent-id");

    //statUrl
    Option statUrl = new TypedOption<String>().setType(String.class).setShortName("stat-url");

    CLI cli = CLIs.create("arthas").addOption(pid).addOption(core).addOption(agent).addOption(target)
            .addOption(telnetPort).addOption(httpPort).addOption(sessionTimeout).addOption(tunnelServer).addOption(agentId).addOption(statUrl);
    //命令行
    CommandLine commandLine = cli.parse(Arrays.asList(args));

    //将参数封装进Configure
    Configure configure = new Configure();
    configure.setJavaPid((Integer) commandLine.getOptionValue("pid"));
    configure.setArthasAgent((String) commandLine.getOptionValue("agent"));
    configure.setArthasCore((String) commandLine.getOptionValue("core"));
    configure.setSessionTimeout((Integer)commandLine.getOptionValue("session-timeout"));
    if (commandLine.getOptionValue("target-ip") == null) {
        throw new IllegalStateException("as.sh is too old to support web console, " +
                "please run the following command to upgrade to latest version:" +
                "\ncurl -sLk https://alibaba.github.io/arthas/install.sh | sh");
    }
    configure.setIp((String) commandLine.getOptionValue("target-ip"));
    configure.setTelnetPort((Integer) commandLine.getOptionValue("telnet-port"));
    configure.setHttpPort((Integer) commandLine.getOptionValue("http-port"));

    configure.setTunnelServer((String) commandLine.getOptionValue("tunnel-server"));
    configure.setAgentId((String) commandLine.getOptionValue("agent-id"));
    configure.setStatUrl((String) commandLine.getOptionValue("stat-url"));
    return configure;
}
Arthas#attachAgent

attach到目标进程。

private void attachAgent(Configure configure) throws Exception {
    VirtualMachineDescriptor virtualMachineDescriptor = null;
    for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) {
        String pid = descriptor.id();
        if (pid.equals(Integer.toString(configure.getJavaPid()))) {
            virtualMachineDescriptor = descriptor;
        }
    }
    VirtualMachine virtualMachine = null;
    try {
        if (null == virtualMachineDescriptor) {
            // 使用 attach(String pid) 这种方式
            virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
        } else {
            virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
        }

        Properties targetSystemProperties = virtualMachine.getSystemProperties();
        //判断当前版本和目标版本是否一致
        String targetJavaVersion = JavaVersionUtils.javaVersionStr(targetSystemProperties);
        String currentJavaVersion = JavaVersionUtils.javaVersionStr();
        if (targetJavaVersion != null && currentJavaVersion != null) {
            if (!targetJavaVersion.equals(currentJavaVersion)) {
                AnsiLog.warn("Current VM java version: {} do not match target VM java version: {}, attach may fail.",
                                currentJavaVersion, targetJavaVersion);
                AnsiLog.warn("Target VM JAVA_HOME is {}, arthas-boot JAVA_HOME is {}, try to set the same JAVA_HOME.",
                                targetSystemProperties.getProperty("java.home"), System.getProperty("java.home"));
            }
        }

        //arthas-agent.jar path
        String arthasAgentPath = configure.getArthasAgent();
        //设置arthas-agent.jar path
        configure.setArthasAgent(encodeArg(arthasAgentPath));
        //设置arthas-core.jar path
        configure.setArthasCore(encodeArg(configure.getArthasCore()));
        /**
         * 加载arthas-agent.jar
         * 其中
         * 参数1:agent路径
         * 参数2:启动参数,规则:arthas-core.jar path + ";" + configure.toString()
         */
        virtualMachine.loadAgent(arthasAgentPath,
                configure.getArthasCore() + ";" + configure.toString());
    } finally {
        if (null != virtualMachine) {
        	//分离
            virtualMachine.detach();
        }
    }
}

AgentBootstrap

premain
JDK1.5加入, 在项目启动时,执行main方法之前对字节码进行增强,适用于需要提前进行埋点、打桩的情况,典型如:分布式链路追踪。

agentmain
JDK1.6加入, premain不够灵活,所以在JDK1.6对其进行了补充扩展。
agentmain 可以在项目运行时,动态地对字节码进行增强,适用于需要动态地修改字节码的情况,比如:热部署,arthas。

public static void premain(String args, Instrumentation inst) {
        main(args, inst);
}
public static void agentmain(String args, Instrumentation inst) {
        main(args, inst);
}
AgentBootstrap#main
private static synchronized void main(String args, final Instrumentation inst) {
    try {
        ps.println("Arthas server agent start...");
        //解码参数:传递的args参数分两个部分:agentJar路径和启动参数
        args = decodeArg(args);
        int index = args.indexOf(';');
        String agentJar = args.substring(0, index);
        final String agentArgs = args.substring(index);

        //agentJar
        File agentJarFile = new File(agentJar);
        if (!agentJarFile.exists()) {
            ps.println("Agent jar file does not exist: " + agentJarFile);
            return;
        }

        //spyJar
        File spyJarFile = new File(agentJarFile.getParentFile(), ARTHAS_SPY_JAR);
        if (!spyJarFile.exists()) {
            ps.println("Spy jar file does not exist: " + spyJarFile);
            return;
        }

        //获取类加载器
        final ClassLoader agentLoader = getClassLoader(inst, spyJarFile, agentJarFile);
        //初始化间谍类
        initSpy(agentLoader);

        //绑定ip,并监听
        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);
    }
}
AgentBootstrap#getClassLoader

注意:使用了BootstrapClassLoader加载间谍类,确保间谍类被加载成功。

private static ClassLoader getClassLoader(Instrumentation inst, File spyJarFile, File agentJarFile) throws Throwable {
    // 将Spy添加到BootstrapClassLoader
    inst.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));

    // 构造自定义的类加载器,尽量减少Arthas对现有工程的侵蚀
    return loadOrDefineClassLoader(agentJarFile);
}
AgentBootstrap#initSpy

特别注意:使用BootstrapClassLoader类加载器加载了间谍类,在增强的时候,会将钩子方法的引用加载到目标类的类加载器中。

private static void initSpy(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
    //加载 【AdviceWeaver】类
    Class<?> adviceWeaverClass = classLoader.loadClass(ADVICEWEAVER);

    //获取AdviceWeaver中钩子方法的引用, 比如:AdviceWeaver#methodOnBegin方法
    Method onBefore = adviceWeaverClass.getMethod(ON_BEFORE, int.class, ClassLoader.class, String.class,
            String.class, String.class, Object.class, Object[].class);
    Method onReturn = adviceWeaverClass.getMethod(ON_RETURN, Object.class);
    Method onThrows = adviceWeaverClass.getMethod(ON_THROWS, Throwable.class);
    Method beforeInvoke = adviceWeaverClass.getMethod(BEFORE_INVOKE, int.class, String.class, String.class, String.class, int.class);
    Method afterInvoke = adviceWeaverClass.getMethod(AFTER_INVOKE, int.class, String.class, String.class, String.class, int.class);
    Method throwInvoke = adviceWeaverClass.getMethod(THROW_INVOKE, int.class, String.class, String.class, String.class, int.class);
    Method reset = AgentBootstrap.class.getMethod(RESET);

    //使用间谍类记录钩子方法的引用
    Spy.initForAgentLauncher(classLoader, onBefore, onReturn, onThrows, beforeInvoke, afterInvoke, throwInvoke, reset);
}

本篇到此为止,剩下的内容下篇分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值