序本文次要钻研一下arthas的ArthasBootstrap
getInstancecom/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的静态方法用户创立ArthasBootstrapArthasBootstrappublic 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,以及设置WriteNonStringKeyAsStringinitSpy 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增加到BootstrapClassLoaderinitArthasEnvironment 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这个程序来配置ArthasEnvironmentinitLogger 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的JoranConfiguratorenhanceClassLoader 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、HistoryManagerImplbind 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办法。
...