先前准备
1:源码下载:https://github.com/alibaba/arthas.git
2:导入到idea,我这里选择直接使用idea下载并打开
3:整体目录结构
attach前准备工作
找到启动类并debug模式运行
com.taobao.arthas.boot.Bootstrap#main
//获取一些配置
CLI cli = CLIConfigurator.define(Bootstrap.class);
拿到启动类上面的一系列注解以及里面的内容,最后返回一个客户端
将客户端包装成一个命令行
//将客户端包装成命令行
CommandLine commandLine = cli.parse(Arrays.asList(args));
将client注入到启动类
CLIConfigurator.inject(commandLine, bootstrap);
筛选进程
pid = ProcessUtils.select(bootstrap.isVerbose(), telnetPortPid, bootstrap.getSelect());
通过jps命令列出进程
Map<Long, String> processMap = listProcessByJps(v);
findJps命令在哪个地方
//获取系统变量
String javaHome = System.getProperty("java.home");
获取jps的命令集合,可能存在着多个,返回第一个
if (jpsList.isEmpty()) {
AnsiLog.debug("Can not find jps under :" + javaHome);
String javaHomeEnv = System.getenv("JAVA_HOME");
AnsiLog.debug("Try to find jps under env JAVA_HOME :" + javaHomeEnv);
for (String path : paths) {
File jpsFile = new File(javaHomeEnv, path);
if (jpsFile.exists()) {
AnsiLog.debug("Found jps: " + jpsFile.getAbsolutePath());
jpsList.add(jpsFile);
}
}
}
返回jps的目录,现在已经知道jps在哪个目录下面了,待会会去执行jps命令,列出所有的java进程
构建 jps 命令,这里的v 代表的是 verbose,相当于我们经常执行的 jps -lvm中的v
执行 jps 命令,
List<String> lines = ExecutingCommand.runNative(command);
中间对 jps返回的数据字符串进行格式化处理
获取到PID,和对应进程的映射关系,存储在一个map中
将进程号以及对应的jps line打印在控制台,然后阻塞住
int count = 1;
for (String process : processMap.values()) {
if (count == 1) {
System.out.println("* [" + count + "]: " + process);
} else {
System.out.println(" [" + count + "]: " + process);
}
count++;
}
如下,接下来叫我们输入对应的进程号,这里选择先前运行的 TestArthasApplication,选择5
接下来做些参数校验
//参数校验
if (choice <= 0 || choice > processMap.size()) {
return -1;
}
............
............
我们按了5后,最终选出来的 PID=12558 ,和我们预期的一样
再来一轮参数校验,-- id校验之类的,以及会提示一些错误信息
private static void checkTelnetPortPid(Bootstrap bootstrap, long telnetPortPid, long targetPid) {
if (telnetPortPid > 0 && targetPid != telnetPortPid) {
AnsiLog.error("The telnet port {} is used by process {} instead of target process {}, you will connect to an unexpected process.",
bootstrap.getTelnetPortOrDefault(), telnetPortPid, targetPid);
AnsiLog.error("1. Try to restart arthas-boot, select process {}, shutdown it first with running the 'stop' command.",
telnetPortPid);
AnsiLog.error("2. Or try to stop the existing arthas instance: java -jar arthas-client.jar 127.0.0.1 {} -c \"stop\"", bootstrap.getTelnetPortOrDefault());
AnsiLog.error("3. Or try to use different telnet port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port -1");
System.exit(1);
}
}
获取到arthas的home,紧接着获取源码目录
CodeSource codeSource = Bootstrap.class.getProtectionDomain().getCodeSource();
file:/C:/Users/zhucc/IdeaProjects/arthas/boot/target/classes/
列出arthas lib 的目录,主要是获取arthas版本
List<String> versionList = listNames(ARTHAS_LIB_DIR);
获取本地的版本arthas
String localLastestVersion = null;
if (!versionList.isEmpty()) {
localLastestVersion = versionList.get(versionList.size() - 1);
}
//获取最新版本的arthas,下载工具
String remoteLastestVersion = DownloadUtils.readLatestReleaseVersion();
public static String readLatestReleaseVersion() {
InputStream inputStream = null;
try {
URLConnection connection = openURLConnection(ARTHAS_LATEST_VERSIONS_URL);
inputStream = connection.getInputStream();
return IOUtils.toString(inputStream).trim();
} catch (Throwable t) {
AnsiLog.error("Can not read arthas version from: " + ARTHAS_LATEST_VERSIONS_URL);
AnsiLog.debug(t);
} finally {
IOUtils.close(inputStream);
}
return null;
}
核对是否要下载
localLastestVersion.compareTo(remoteLastestVersion) < 0
else {
if (remoteLastestVersion != null) {
if (localLastestVersion.compareTo(remoteLastestVersion) < 0) {
AnsiLog.info("local lastest version: {}, remote lastest version: {}, try to download from remote.",
localLastestVersion, remoteLastestVersion);
needDownload = true;
}
}
}
获取到arthas的觉得路径
check 进程id
if (telnetPortPid > 0 && pid == telnetPortPid) {
AnsiLog.info("The target process already listen port {}, skip attach.", bootstrap.getTelnetPortOrDefault());
}
开始类加载工作
通过类url类加载器来加载磁盘上的文件,获取文件
通过反射获取到 main 方法
在命令中拼装参数
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());
}
在当前线程中设置一个上下文,并invoke刚刚 telnetConsole.main方法
在telnetConsole.main 中断点,已经进入
com.taobao.arthas.client.TelnetConsole#process(java.lang.String[], java.awt.event.ActionListener)
调用启动类一样的接口,做参数设置,等等,这里不再赘述, 紧接着一系列参数校验
TelnetConsole telnetConsole = new TelnetConsole();
CLI cli = CLIConfigurator.define(TelnetConsole.class);
CommandLine commandLine = cli.parse(Arrays.asList(args));
CLIConfigurator.inject(commandLine, telnetConsole);
获取一个终端 Terminal
final ConsoleReader consoleReader = new ConsoleReader(System.in, System.out);
consoleReader.setHandleUserInterrupt(true);
Terminal terminal = consoleReader.getTerminal();
// support catch ctrl+c event
terminal.disableInterruptCharacter();
if (terminal instanceof UnixTerminal) {
((UnixTerminal) terminal).disableLitteralNextCharacter();
}
连接终端,连接上后,返回成功 – STATUS_OK
org.apache.commons.net.SocketClient#connect(java.lang.String, int)