arthas启动
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
选择应用的java进程:
$ java -jar arthas-boot.jar
[1]: 35542
[2]: 71560 arthas-demo.jar
Bootstrap
执行 java -jar arthas-boot.jar之后,便来到了Bootstrap#main方法中。
静态代码块
static {
//创建 arthas lib 目录 :C:\Users\用户\.arthas\lib
ARTHAS_LIB_DIR = new File(
System.getProperty("user.home") + File.separator + ".arthas" + File.separator + "lib");
try {
ARTHAS_LIB_DIR.mkdirs();
} catch (Throwable t) {
//ignore
}
/**
* 如果lib目录不存在,则在操作系统的缓存的临时目录,创建lib目录
* 其中:
* Windows的缓存目录为:C:\Users\用户\AppData\Local\Temp\
*/
if (!ARTHAS_LIB_DIR.exists()) {
// try to set a temp directory
ARTHAS_LIB_DIR = new File(System.getProperty("java.io.tmpdir") + File.separator + ".arthas" + File.separator + "lib");
try {
ARTHAS_LIB_DIR.mkdirs();
} catch (Throwable e) {
// ignore
}
}
//没有创建成功lib目录,则抛出异常
if (!ARTHAS_LIB_DIR.exists()) {
System.err.println("Can not find directory to save arthas lib. please try to set user home by -Duser.home=");
}
}
Bootstrap#main
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException,
ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
//当前类的Package
Package bootstrapPackage = Bootstrap.class.getPackage();
if (bootstrapPackage != null) {
//arthas版本
String arthasBootVersion = bootstrapPackage.getImplementationVersion();
if (arthasBootVersion != null) {
AnsiLog.info("arthas-boot version: " + arthasBootVersion);
}
}
String mavenMetaData = null;
//创建启动类
Bootstrap bootstrap = new Bootstrap();
//提取Bootstrap中的元信息
CLI cli = CLIConfigurator.define(Bootstrap.class);
//解析java -jar arthas-boot.jar 后面跟的参数
CommandLine commandLine = cli.parse(Arrays.asList(args));
//将命令行参数注入到Bootstrap中
try {
CLIConfigurator.inject(commandLine, bootstrap);
} catch (Throwable e) {
e.printStackTrace();
System.out.println(usage(cli));
System.exit(1);
}
//存在-v选项,设置打印所有日志内容
if (bootstrap.isVerbose()) {
AnsiLog.level(Level.ALL);
}
//存在-p选项,打印帮助信息,并退出
if (bootstrap.isHelp()) {
System.out.println(usage(cli));
//正常退出
System.exit(0);
}
/**
* 设置仓库镜像,
* 如果是北京时间,默认使用aliyun的仓库镜像,否则使用maven的中心仓库
*/
if (bootstrap.getRepoMirror() == null || bootstrap.getRepoMirror().trim().isEmpty()) {
bootstrap.setRepoMirror("center");
// if timezone is +0800, default repo mirror is aliyun
if (TimeUnit.MILLISECONDS.toHours(TimeZone.getDefault().getOffset(System.currentTimeMillis())) == 8) {
bootstrap.setRepoMirror("aliyun");
}
}
AnsiLog.debug("Repo mirror:" + bootstrap.getRepoMirror());
//存在-version选项,列出版本信息,并退出。
if (bootstrap.isVersions()) {
if (mavenMetaData == null) {
mavenMetaData = DownloadUtils.readMavenMetaData(bootstrap.getRepoMirror(), bootstrap.isuseHttp());
}
System.out.println(UsageRender.render(listVersions(mavenMetaData)));
System.exit(0);
}
//jdk6 or jdk7使用http
if (JavaVersionUtils.isJava6() || JavaVersionUtils.isJava7()) {
bootstrap.setuseHttp(true);
AnsiLog.debug("Java version is {}, only support http, set useHttp to true.",
JavaVersionUtils.javaVersionStr());
}
//判断端口号是否被占用
// check telnet/http port
int telnetPortPid = -1;
int httpPortPid = -1;
if (bootstrap.getTelnetPort() > 0) {
//查找telnet端口号(默认3658)对应的进程id
telnetPortPid = SocketUtils.findTcpListenProcess(bootstrap.getTelnetPort());
if (telnetPortPid > 0) {
//打印端口被占用日志
AnsiLog.info("Process {} already using port {}", telnetPortPid, bootstrap.getTelnetPort());
}
}
if (bootstrap.getHttpPort() > 0) {
//查找http端口号(默认8563)对应的的进程id
httpPortPid = SocketUtils.findTcpListenProcess(bootstrap.getHttpPort());
if (httpPortPid > 0) {
//打印端口被占用日志
AnsiLog.info("Process {} already using port {}", httpPortPid, bootstrap.getHttpPort());
}
}
//选择要attach的进程号
int pid = bootstrap.getPid();
// select pid
if (pid < 0) {
try {
//列出所有java进程,并选择要attach的进程号
pid = ProcessUtils.select(bootstrap.isVerbose(), telnetPortPid);
} catch (InputMismatchException e) {
System.out.println("Please input an integer to select pid.");
System.exit(1);
}
if (pid < 0) {
System.out.println("Please select an available pid.");
System.exit(1);
}
}
if (telnetPortPid > 0 && pid != telnetPortPid) {
AnsiLog.error("Target process {} is not the process using port {}, you will connect to an unexpected process.",
pid, bootstrap.getTelnetPort());
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 use different telnet port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port -1");
System.exit(1);
}
if (httpPortPid > 0 && pid != httpPortPid) {
AnsiLog.error("Target process {} is not the process using port {}, you will connect to an unexpected process.",
pid, bootstrap.getHttpPort());
AnsiLog.error("1. Try to restart arthas-boot, select process {}, shutdown it first with running the 'stop' command.",
httpPortPid);
AnsiLog.error("2. Or try to use different http port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port 9999", httpPortPid);
System.exit(1);
}
//创建arthas home.
//默认的home:C:/Users/user/.arthas/lib/version/arthas
File arthasHomeDir = null;
//arthas home存在,则直接使用
if (bootstrap.getArthasHome() != null) {
verifyArthasHome(bootstrap.getArthasHome());
arthasHomeDir = new File(bootstrap.getArthasHome());
}
//arthas home不存在
if (arthasHomeDir == null && bootstrap.getUseVersion() != null) {
// try to find from ~/.arthas/lib
File specialVersionDir = new File(System.getProperty("user.home"), ".arthas" + File.separator + "lib"
+ File.separator + bootstrap.getUseVersion() + File.separator + "arthas");
//
if (!specialVersionDir.exists()) {
//从远程下载arthas
DownloadUtils.downArthasPackaging(bootstrap.getRepoMirror(), bootstrap.isuseHttp(),
bootstrap.getUseVersion(), ARTHAS_LIB_DIR.getAbsolutePath());
}
verifyArthasHome(specialVersionDir.getAbsolutePath());
arthasHomeDir = specialVersionDir;
}
// Try set the directory where arthas-boot.jar is located to arhtas home
// 尝试将arthas-boot.jar所在的目录设置为arhtas home。
if (arthasHomeDir == null) {
CodeSource codeSource = Bootstrap.class.getProtectionDomain().getCodeSource();
if (codeSource != null) {
try {
// https://stackoverflow.com/a/17870390
File bootJarPath = new File(codeSource.getLocation().toURI().getSchemeSpecificPart());
verifyArthasHome(bootJarPath.getParent());
arthasHomeDir = bootJarPath.getParentFile();
} catch (Throwable e) {
// ignore
}
}
}
//尝试从远程下载arthas
if (arthasHomeDir == null) {
//校验lib目录是否存在
boolean checkFile = ARTHAS_LIB_DIR.exists() || ARTHAS_LIB_DIR.mkdirs();
if(!checkFile){
AnsiLog.error("cannot create directory {}: maybe permission denied", ARTHAS_LIB_DIR.getAbsolutePath());
System.exit(1);
}
/**
* <pre>
* 1. get local latest version
* 2. get remote latest version
* 3. compare two version
* </pre>
*/
List<String> versionList = listNames(ARTHAS_LIB_DIR);
Collections.sort(versionList);
String localLastestVersion = null;
if (!versionList.isEmpty()) {
localLastestVersion = versionList.get(versionList.size() - 1);
}
//读取maven的元数据(maven-metadata.xml文件)
if (mavenMetaData == null) {
mavenMetaData = DownloadUtils.readMavenMetaData(bootstrap.getRepoMirror(), bootstrap.isuseHttp());
}
//获取发布的最新版本
String remoteLastestVersion = DownloadUtils.readMavenReleaseVersion(mavenMetaData);
//是否需要下载
boolean needDownload = false;
if (localLastestVersion == null) {
if (remoteLastestVersion == null) {
// exit
AnsiLog.error("Can not find Arthas under local: {} and remote maven repo mirror: {}", ARTHAS_LIB_DIR,
bootstrap.getRepoMirror());
AnsiLog.error(
"Unable to download arthas from remote server, please download the full package according to wiki: https://github.com/alibaba/arthas");
System.exit(1);
} else {
needDownload = true;
}
} 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;
}
}
}
//需要下载
if (needDownload) {
//尝试从远程服务器下载阿尔萨斯。
DownloadUtils.downArthasPackaging(bootstrap.getRepoMirror(), bootstrap.isuseHttp(),
remoteLastestVersion, ARTHAS_LIB_DIR.getAbsolutePath());
localLastestVersion = remoteLastestVersion;
}
// get the latest version
arthasHomeDir = new File(ARTHAS_LIB_DIR, localLastestVersion + File.separator + "arthas");
}
verifyArthasHome(arthasHomeDir.getAbsolutePath());
AnsiLog.info("arthas home: " + arthasHomeDir);
//启动arthas
if (telnetPortPid > 0 && pid == telnetPortPid) {
//arthas已经启动。
AnsiLog.info("The target process already listen port {}, skip attach.", bootstrap.getTelnetPort());
} else {
//arthas-core还没有启动,则启动arthas-core.jar
List<String> attachArgs = new ArrayList<String>();
attachArgs.add("-jar");
attachArgs.add(new File(arthasHomeDir, "arthas-core.jar").getAbsolutePath());
attachArgs.add("-pid");
attachArgs.add("" + pid);
attachArgs.add("-target-ip");
attachArgs.add(bootstrap.getTargetIp());
attachArgs.add("-telnet-port");
attachArgs.add("" + bootstrap.getTelnetPort());
attachArgs.add("-http-port");
attachArgs.add("" + bootstrap.getHttpPort());
attachArgs.add("-core");
attachArgs.add(new File(arthasHomeDir, "arthas-core.jar").getAbsolutePath());
attachArgs.add("-agent");
attachArgs.add(new File(arthasHomeDir, "arthas-agent.jar").getAbsolutePath());
if (bootstrap.getSessionTimeout() != null) {
attachArgs.add("-session-timeout");
attachArgs.add("" + bootstrap.getSessionTimeout());
}
if (bootstrap.getTunnelServer() != null) {
attachArgs.add("-tunnel-server");
attachArgs.add(bootstrap.getTunnelServer());
}
if (bootstrap.getAgentId() != null) {
attachArgs.add("-agent-id");
attachArgs.add(bootstrap.getAgentId());
}
if (bootstrap.getStatUrl() != null) {
attachArgs.add("-stat-url");
attachArgs.add(bootstrap.getStatUrl());
}
AnsiLog.info("Try to attach process " + pid);
AnsiLog.debug("Start arthas-core.jar args: " + attachArgs);
//启动加载arthas-core.jar
ProcessUtils.startArthasCore(pid, attachArgs);
AnsiLog.info("Attach process {} success.", pid);
}
//如果只是attach java进程,到这儿就结束了,就不加载后面的telnet 客户端了。
if (bootstrap.isAttachOnly()) {
System.exit(0);
}
//查找arthas-client.jar,并反射调用TelnetConsole#main,启动telnet client
URLClassLoader classLoader = new URLClassLoader(
new URL[] { new File(arthasHomeDir, "arthas-client.jar").toURI().toURL() });
Class<?> telnetConsoleClas = classLoader.loadClass("com.taobao.arthas.client.TelnetConsole");
Method mainMethod = telnetConsoleClas.getMethod("main", String[].class);
List<String> telnetArgs = new ArrayList<String>();
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());
}
// telnet port ,ip
telnetArgs.add(bootstrap.getTargetIp());
telnetArgs.add("" + bootstrap.getTelnetPort());
AnsiLog.info("arthas-client connect {} {}", bootstrap.getTargetIp(), bootstrap.getTelnetPort());
AnsiLog.debug("Start arthas-client.jar args: " + telnetArgs);
// fix https://github.com/alibaba/arthas/issues/833
Thread.currentThread().setContextClassLoader(classLoader);
mainMethod.invoke(null, new Object[] { telnetArgs.toArray(new String[0]) });
}
SocketUtils#findTcpListenProcess
查找端口所对应的进程id
实现原理
如果是window系统:使用 netstat -ano -p TCP命令
如果是linux系统:使用 lsof -t -s TCP:LISTEN -i TCP: port 命令
public static int findTcpListenProcess(int port) {
try {
/**
* window下使用 netstat -ano -p TCP命令 查看tcp连接
* 查询回来的信息包含五部分,例如:TCP 0.0.0.0:49168 0.0.0.0:0 LISTENING 476
*/
if (OSUtils.isWindows()) {
String[] command = { "netstat", "-ano", "-p", "TCP" };
List<String> lines = ExecutingCommand.runNative(command);
for (String line : lines) {
if (line.contains("LISTENING")) {
// TCP 0.0.0.0:49168 0.0.0.0:0 LISTENING 476
String[] strings = line.trim().split("\\s+");
if (strings.length == 5) {
if (strings[1].endsWith(":" + port)) {
return Integer.parseInt(strings[4]);
}
}
}
}
}
/**
* 如果是linux系统,使用lsof -t -s TCP:LISTEN -i TCP: port 命令,查看端口号对应的进程id。
*/
if (OSUtils.isLinux() || OSUtils.isMac()) {
String pid = ExecutingCommand.getFirstAnswer("lsof -t -s TCP:LISTEN -i TCP:" + port);
if (!pid.trim().isEmpty()) {
return Integer.parseInt(pid);
}
}
} catch (Throwable e) {
// ignore
}
return -1;
}
ProcessUtils#select
1、使用java 的 jps -l 命令查找所有java 进程,并列出。
2、用户选择要attach的目标进程。
public static int select(boolean v, int telnetPortPid) throws InputMismatchException {
//jps命令查找到的所有java进程,其中key:进程号,value:java进程
Map<Integer, String> processMap = listProcessByJps(v);
// Put the port that is already listening at the first
//把已经监听的端口放在第一个
if (telnetPortPid > 0 && processMap.containsKey(telnetPortPid)) {
String telnetPortProcess = processMap.get(telnetPortPid);
processMap.remove(telnetPortPid);
Map<Integer, String> newProcessMap = new LinkedHashMap<Integer, String>();
newProcessMap.put(telnetPortPid, telnetPortProcess);
newProcessMap.putAll(processMap);
processMap = newProcessMap;
}
//没有找到Java进程
if (processMap.isEmpty()) {
AnsiLog.info("Can not find java process. Try to pass <pid> in command line.");
return -1;
}
/**
* 打印找到的所有进程,例如:
* [1]: 12744 org.jetbrains.jps.cmdline.Launcher
* [2]: 1672
*/
AnsiLog.info("Found existing java process, please choose one and hit RETURN.");
// print list
int count = 1;
for (String process : processMap.values()) {
if (count == 1) {
System.out.println("* [" + count + "]: " + process);
} else {
System.out.println(" [" + count + "]: " + process);
}
count++;
}
//创建扫描器,等待接受用户输入数字
// read choice
String line = new Scanner(System.in).nextLine();
if (line.trim().isEmpty()) {
// get the first process id
return processMap.keySet().iterator().next();
}
//用户键入的数字
int choice = new Scanner(line).nextInt();
if (choice <= 0 || choice > processMap.size()) {
return -1;
}
//查找数字编号对应的进程id,并返回
Iterator<Integer> idIter = processMap.keySet().iterator();
for (int i = 1; i <= choice; ++i) {
if (i == choice) {
return idIter.next();
}
idIter.next();
}
return -1;
}
ProcessUtils#startArthasCore
public static void startArthasCore(int targetPid, List<String> attachArgs) {
// find java/java.exe, then try to find tools.jar
String javaHome = findJavaHome();
// find java/java.exe
File javaPath = findJava();
if (javaPath == null) {
throw new IllegalArgumentException(
"Can not find java/java.exe executable file under java home: " + javaHome);
}
//find tools.jar
File toolsJar = findToolsJar();
if (JavaVersionUtils.isLessThanJava9()) {
if (toolsJar == null || !toolsJar.exists()) {
throw new IllegalArgumentException("Can not find tools.jar under java home: " + javaHome);
}
}
//将java.exe路径放入command集合
List<String> command = new ArrayList<String>();
command.add(javaPath.getAbsolutePath());
//将tools.jar路径放入command集合
if (toolsJar != null && toolsJar.exists()) {
//在tools.jar中查找相关类。
command.add("-Xbootclasspath/a:" + toolsJar.getAbsolutePath());
}
command.addAll(attachArgs);
// "${JAVA_HOME}"/bin/java \
// ${opts} \
// -jar "${arthas_lib_dir}/arthas-core.jar" \
// -pid ${TARGET_PID} \
// -target-ip ${TARGET_IP} \
// -telnet-port ${TELNET_PORT} \
// -http-port ${HTTP_PORT} \
// -core "${arthas_lib_dir}/arthas-core.jar" \
// -agent "${arthas_lib_dir}/arthas-agent.jar"
//创建一个子进程
ProcessBuilder pb = new ProcessBuilder(command);
try {
//启动子进程
final Process proc = pb.start();
//开启一个线程,接受子进程的输入流
Thread redirectStdout = new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = proc.getInputStream();
try {
IOUtils.copy(inputStream, System.out);
} catch (IOException e) {
IOUtils.close(inputStream);
}
}
});
//开启一个线程,接受子进程的错误流
Thread redirectStderr = new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = proc.getErrorStream();
try {
IOUtils.copy(inputStream, System.err);
} catch (IOException e) {
IOUtils.close(inputStream);
}
}
});
//启动线程,并加入main线程
redirectStdout.start();
redirectStderr.start();
redirectStdout.join();
redirectStderr.join();
//获取子进程的退出值,如果值不等于0,退出子进程。
//exitValue 表示子进程System.exit()中的值
int exitValue = proc.exitValue();
if (exitValue != 0) {
AnsiLog.error("attach fail, targetPid: " + targetPid);
System.exit(1);
}
} catch (Throwable e) {
// ignore
}
}