本文次要钻研一下arthas的ArthasBootstrap

getInstance

com/taobao/arthas/core/server/ArthasBootstrap.java

    /**     * 单例     *     * @param instrumentation JVM加强     * @return ArthasServer单例     * @throws Throwable     */    public synchronized static ArthasBootstrap getInstance(Instrumentation instrumentation, Map<String, String> args) throws Throwable {        if (arthasBootstrap == null) {            arthasBootstrap = new ArthasBootstrap(instrumentation, args);        }        return arthasBootstrap;    }
ArthasBootstrap提供了getInstance的静态方法用户创立ArthasBootstrap

ArthasBootstrap

public class ArthasBootstrap {    private static final String ARTHAS_SPY_JAR = "arthas-spy.jar";    public static final String ARTHAS_HOME_PROPERTY = "arthas.home";    private static String ARTHAS_HOME = null;    public static final String CONFIG_NAME_PROPERTY = "arthas.config.name";    public static final String CONFIG_LOCATION_PROPERTY = "arthas.config.location";    public static final String CONFIG_OVERRIDE_ALL = "arthas.config.overrideAll";    private static ArthasBootstrap arthasBootstrap;    private ArthasEnvironment arthasEnvironment;    private Configure configure;    private AtomicBoolean isBindRef = new AtomicBoolean(false);    private Instrumentation instrumentation;    private InstrumentTransformer classLoaderInstrumentTransformer;    private Thread shutdown;    private ShellServer shellServer;    private ScheduledExecutorService executorService;    private SessionManager sessionManager;    private TunnelClient tunnelClient;    private File outputPath;    private static LoggerContext loggerContext;    private EventExecutorGroup workerGroup;    private Timer timer = new Timer("arthas-timer", true);    private TransformerManager transformerManager;    private ResultViewResolver resultViewResolver;    private HistoryManager historyManager;    private HttpApiHandler httpApiHandler;    private HttpSessionManager httpSessionManager;    private SecurityAuthenticator securityAuthenticator;    private ArthasBootstrap(Instrumentation instrumentation, Map<String, String> args) throws Throwable {        this.instrumentation = instrumentation;        initFastjson();        // 1. initSpy()        initSpy();        // 2. ArthasEnvironment        initArthasEnvironment(args);        String outputPathStr = configure.getOutputPath();        if (outputPathStr == null) {            outputPathStr = ArthasConstants.ARTHAS_OUTPUT;        }        outputPath = new File(outputPathStr);        outputPath.mkdirs();        // 3. init logger        loggerContext = LogUtil.initLogger(arthasEnvironment);        // 4. 加强ClassLoader        enhanceClassLoader();        // 5. init beans        initBeans();        // 6. start agent 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);    }    //......}    
ArthasBootstrap的结构器执行initFastjson、initSpy、initArthasEnvironment、LogUtil.initLogger(arthasEnvironment)、enhanceClassLoader、initBeans、bind,最初注册shutdownHook来执行destroy办法

initFastjson

    private void initFastjson() {        // ignore getter error #1661        // #2081        JSON.config(JSONWriter.Feature.IgnoreErrorGetter, JSONWriter.Feature.WriteNonStringKeyAsString);    }
initFastjson这里应用fastjson2的config办法来疏忽getter的error,以及设置WriteNonStringKeyAsString

initSpy

    private void initSpy() throws Throwable {        // TODO init SpyImpl ?        // 将Spy增加到BootstrapClassLoader        ClassLoader parent = ClassLoader.getSystemClassLoader().getParent();        Class<?> spyClass = null;        if (parent != null) {            try {                spyClass =parent.loadClass("java.arthas.SpyAPI");            } catch (Throwable e) {                // ignore            }        }        if (spyClass == null) {            CodeSource codeSource = ArthasBootstrap.class.getProtectionDomain().getCodeSource();            if (codeSource != null) {                File arthasCoreJarFile = new File(codeSource.getLocation().toURI().getSchemeSpecificPart());                File spyJarFile = new File(arthasCoreJarFile.getParentFile(), ARTHAS_SPY_JAR);                instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));            } else {                throw new IllegalStateException("can not find " + ARTHAS_SPY_JAR);            }        }    }
initSpy将Spy增加到BootstrapClassLoader

initArthasEnvironment

    private void initArthasEnvironment(Map<String, String> argsMap) throws IOException {        if (arthasEnvironment == null) {            arthasEnvironment = new ArthasEnvironment();        }        /**         * <pre>         * 脚本里传过来的配置项,即命令行参数 > System Env > System Properties > arthas.properties         * arthas.properties 提供一个配置项,能够反转优先级。 arthas.config.overrideAll=true         * https://github.com/alibaba/arthas/issues/986         * </pre>         */        Map<String, Object> copyMap;        if (argsMap != null) {            copyMap = new HashMap<String, Object>(argsMap);            // 增加 arthas.home            if (!copyMap.containsKey(ARTHAS_HOME_PROPERTY)) {                copyMap.put(ARTHAS_HOME_PROPERTY, arthasHome());            }        } else {            copyMap = new HashMap<String, Object>(1);            copyMap.put(ARTHAS_HOME_PROPERTY, arthasHome());        }        MapPropertySource mapPropertySource = new MapPropertySource("args", copyMap);        arthasEnvironment.addFirst(mapPropertySource);        tryToLoadArthasProperties();        configure = new Configure();        BinderUtils.inject(arthasEnvironment, configure);    }
initArthasEnvironment按命令行参数 > System Env > System Properties > arthas.properties这个程序来配置ArthasEnvironment

initLogger

    public static LoggerContext initLogger(ArthasEnvironment env) {        String loggingConfig = env.resolvePlaceholders(LOGGING_CONFIG);        if (loggingConfig == null || loggingConfig.trim().isEmpty()) {            return null;        }        AnsiLog.debug("arthas logging file: " + loggingConfig);        File configFile = new File(loggingConfig);        if (!configFile.isFile()) {            AnsiLog.error("can not find arthas logging config: " + loggingConfig);            return null;        }        try {            LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();            loggerContext.reset();            String fileName = env.getProperty(FILE_NAME_PROPERTY);            if (fileName != null) {                loggerContext.putProperty(ARTHAS_LOG_FILE, fileName);            }            String filePath = env.getProperty(FILE_PATH_PROPERTY);            if (filePath != null) {                loggerContext.putProperty(ARTHAS_LOG_PATH, filePath);            }            JoranConfigurator configurator = new JoranConfigurator();            configurator.setContext(loggerContext);            configurator.doConfigure(configFile.toURI().toURL()); // load logback xml file            // 查找 arthas.log appender            Iterator<Appender<ILoggingEvent>> appenders = loggerContext.getLogger("root").iteratorForAppenders();            while (appenders.hasNext()) {                Appender<ILoggingEvent> appender = appenders.next();                if (appender instanceof RollingFileAppender) {                    RollingFileAppender fileAppender = (RollingFileAppender) appender;                    if ("ARTHAS".equalsIgnoreCase(fileAppender.getName())) {                        logFile = new File(fileAppender.getFile()).getCanonicalPath();                    }                }            }            return loggerContext;        } catch (Throwable e) {            AnsiLog.error("try to load arthas logging config file error: " + configFile, e);        }        return null;    }
initLogger从env获取相应的日志配置,而后设置到LoggerContext,而后设置到logback的JoranConfigurator

enhanceClassLoader

    void enhanceClassLoader() throws IOException, UnmodifiableClassException {        if (configure.getEnhanceLoaders() == null) {            return;        }        Set<String> loaders = new HashSet<String>();        for (String s : configure.getEnhanceLoaders().split(",")) {            loaders.add(s.trim());        }        // 加强 ClassLoader#loadClsss ,解决一些ClassLoader加载不到 SpyAPI的问题        // https://github.com/alibaba/arthas/issues/1596        byte[] classBytes = IOUtils.getBytes(ArthasBootstrap.class.getClassLoader()                .getResourceAsStream(ClassLoader_Instrument.class.getName().replace('.', '/') + ".class"));        SimpleClassMatcher matcher = new SimpleClassMatcher(loaders);        InstrumentConfig instrumentConfig = new InstrumentConfig(AsmUtils.toClassNode(classBytes), matcher);        InstrumentParseResult instrumentParseResult = new InstrumentParseResult();        instrumentParseResult.addInstrumentConfig(instrumentConfig);        classLoaderInstrumentTransformer = new InstrumentTransformer(instrumentParseResult);        instrumentation.addTransformer(classLoaderInstrumentTransformer, true);        if (loaders.size() == 1 && loaders.contains(ClassLoader.class.getName())) {            // 如果只加强 java.lang.ClassLoader,能够缩小查找过程            instrumentation.retransformClasses(ClassLoader.class);        } else {            InstrumentationUtils.trigerRetransformClasses(instrumentation, loaders);        }    }
enhanceClassLoader会应用instrumentation来加强configure.getEnhanceLoaders()

initBeans

    private void initBeans() {        this.resultViewResolver = new ResultViewResolver();        this.historyManager = new HistoryManagerImpl();    }
initBeans则创立ResultViewResolver、HistoryManagerImpl

bind

    private void bind(Configure configure) throws Throwable {        long start = System.currentTimeMillis();        if (!isBindRef.compareAndSet(false, true)) {            throw new IllegalStateException("already bind");        }        // init random port        if (configure.getTelnetPort() != null && configure.getTelnetPort() == 0) {            int newTelnetPort = SocketUtils.findAvailableTcpPort();            configure.setTelnetPort(newTelnetPort);            logger().info("generate random telnet port: " + newTelnetPort);        }        if (configure.getHttpPort() != null && configure.getHttpPort() == 0) {            int newHttpPort = SocketUtils.findAvailableTcpPort();            configure.setHttpPort(newHttpPort);            logger().info("generate random http port: " + newHttpPort);        }        // try to find appName        if (configure.getAppName() == null) {            configure.setAppName(System.getProperty(ArthasConstants.PROJECT_NAME,                    System.getProperty(ArthasConstants.SPRING_APPLICATION_NAME, null)));        }        try {            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 options = new ShellServerOptions()                            .setInstrumentation(instrumentation)                            .setPid(PidUtils.currentLongPid())                            .setWelcomeMessage(ArthasBanner.welcome());            if (configure.getSessionTimeout() != null) {                options.setSessionTimeout(configure.getSessionTimeout() * 1000);            }            this.httpSessionManager = new HttpSessionManager();            if (IPUtils.isAllZeroIP(configure.getIp()) && StringUtils.isBlank(configure.getPassword())) {                // 当 listen 0.0.0.0 时,强制生成明码,避免被近程连贯                String errorMsg = "Listening on 0.0.0.0 is very dangerous! External users can connect to your machine! "                        + "No password is currently configured. " + "Therefore, a default password is generated, "                        + "and clients need to use the password to connect!";                AnsiLog.error(errorMsg);                configure.setPassword(StringUtils.randomString(64));                AnsiLog.error("Generated arthas password: " + configure.getPassword());                logger().error(errorMsg);                logger().info("Generated arthas password: " + configure.getPassword());            }            this.securityAuthenticator = new SecurityAuthenticatorImpl(configure.getUsername(), configure.getPassword());            shellServer = new ShellServerImpl(options);            List<String> disabledCommands = new ArrayList<String>();            if (configure.getDisabledCommands() != null) {                String[] strings = StringUtils.tokenizeToStringArray(configure.getDisabledCommands(), ",");                if (strings != null) {                    disabledCommands.addAll(Arrays.asList(strings));                }            }            BuiltinCommandPack builtinCommands = new BuiltinCommandPack(disabledCommands);            List<CommandResolver> resolvers = new ArrayList<CommandResolver>();            resolvers.add(builtinCommands);            //worker group            workerGroup = new NioEventLoopGroup(new DefaultThreadFactory("arthas-TermServer", true));            // TODO: discover user provided command resolver            if (configure.getTelnetPort() != null && configure.getTelnetPort() > 0) {                logger().info("try to bind telnet server, host: {}, port: {}.", configure.getIp(), configure.getTelnetPort());                shellServer.registerTermServer(new HttpTelnetTermServer(configure.getIp(), configure.getTelnetPort(),                        options.getConnectionTimeout(), workerGroup, httpSessionManager));            } else {                logger().info("telnet port is {}, skip bind telnet server.", configure.getTelnetPort());            }            if (configure.getHttpPort() != null && configure.getHttpPort() > 0) {                logger().info("try to bind http server, host: {}, port: {}.", configure.getIp(), configure.getHttpPort());                shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(),                        options.getConnectionTimeout(), workerGroup, httpSessionManager));            } else {                // listen local address in VM communication                if (configure.getTunnelServer() != null) {                    shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(),                            options.getConnectionTimeout(), workerGroup, httpSessionManager));                }                logger().info("http port is {}, skip bind http server.", configure.getHttpPort());            }            for (CommandResolver resolver : resolvers) {                shellServer.registerCommandResolver(resolver);            }            shellServer.listen(new BindHandler(isBindRef));            if (!isBind()) {                throw new IllegalStateException("Arthas failed to bind telnet or http port! Telnet port: "                        + String.valueOf(configure.getTelnetPort()) + ", http port: "                        + String.valueOf(configure.getHttpPort()));            }            //http api session manager            sessionManager = new SessionManagerImpl(options, shellServer.getCommandManager(), shellServer.getJobController());            //http api handler            httpApiHandler = new HttpApiHandler(historyManager, sessionManager);            logger().info("as-server listening on network={};telnet={};http={};timeout={};", configure.getIp(),                    configure.getTelnetPort(), configure.getHttpPort(), options.getConnectionTimeout());            // 异步回报启动次数            if (configure.getStatUrl() != null) {                logger().info("arthas stat url: {}", configure.getStatUrl());            }            UserStatUtil.setStatUrl(configure.getStatUrl());            UserStatUtil.setAgentId(configure.getAgentId());            UserStatUtil.arthasStart();            try {                SpyAPI.init();            } catch (Throwable e) {                // ignore            }            logger().info("as-server started in {} ms", System.currentTimeMillis() - start);        } catch (Throwable e) {            logger().error("Error during start as-server", e);            destroy();            throw e;        }    }
bind办法次要是启动AgentServer,若有设置tunnelServer则初始化tunnelClient,而后创立ShellServerImpl,对于有设置telnetPort的则注册HttpTelnetTermServer,对于有设置httpPort的则注册HttpTermServer,而后执行shellServer.listen,UserStatUtil.arthasStart(),最初执行SpyAPI.init()

destroy

    public void destroy() {        if (shellServer != null) {            shellServer.close();            shellServer = null;        }        if (sessionManager != null) {            sessionManager.close();            sessionManager = null;        }        if (this.httpSessionManager != null) {            httpSessionManager.stop();        }        if (timer != null) {            timer.cancel();        }        if (this.tunnelClient != null) {            try {                tunnelClient.stop();            } catch (Throwable e) {                logger().error("stop tunnel client error", e);            }        }        if (executorService != null) {            executorService.shutdownNow();        }        if (transformerManager != null) {            transformerManager.destroy();        }        if (classLoaderInstrumentTransformer != null) {            instrumentation.removeTransformer(classLoaderInstrumentTransformer);        }        // clear the reference in Spy class.        cleanUpSpyReference();        shutdownWorkGroup();        UserStatUtil.destroy();        if (shutdown != null) {            try {                Runtime.getRuntime().removeShutdownHook(shutdown);            } catch (Throwable t) {                // ignore            }        }        logger().info("as-server destroy completed.");        if (loggerContext != null) {            loggerContext.stop();        }    }
destroy办法执行shellServer.close()、sessionManager.close()、httpSessionManager.stop()、timer.cancel()、tunnelClient.stop()、executorService.shutdownNow()、transformerManager.destroy()、instrumentation.removeTransformer(classLoaderInstrumentTransformer)、cleanUpSpyReference()、shutdownWorkGroup()、UserStatUtil.destroy()、Runtime.getRuntime().removeShutdownHook、loggerContext.stop()

小结

ArthasBootstrap的结构器执行initFastjson、initSpy、initArthasEnvironment、LogUtil.initLogger(arthasEnvironment)、enhanceClassLoader、initBeans、bind,最初注册shutdownHook来执行destroy办法。