共计 14290 个字符,预计需要花费 36 分钟才能阅读完成。
序
本文次要钻研一下 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 办法。