执行java -jar 之后,便会进入Arthas|#main,下面对其进行详细分析。
Arthas
Arthas#main
创建Arthas。
当发生异常时,打印错误日志,并退出System.exit(-1);
在arthas-boot.jar 进程中会接受到exit code,当exit code = -1时,会结束arthas 进程。
主要做了两件事1、解析参数,2、attach到目标进程。
public static void main(String[] args) {
try {
new Arthas(args);
} catch (Throwable t) {
AnsiLog.error("Start arthas failed, exception stack trace: ");
t.printStackTrace();
System.exit(-1);
}
}
private Arthas(String[] args) throws Exception {
//解析
attachAgent(parse(args));
}
Arthas#parse
解析启动参数,并封装进Configure
private Configure parse(String[] args) {
//目标进程id
Option pid = new TypedOption<Integer>().setType(Integer.class).setShortName("pid").setRequired(true);
//arthas-core.jar路径
Option core = new TypedOption<String>().setType(String.class).setShortName("core").setRequired(true);
//arthas-agent.jar路径
Option agent = new TypedOption<String>().setType(String.class).setShortName("agent").setRequired(true);
//目标进程的ip
Option target = new TypedOption<String>().setType(String.class).setShortName("target-ip");
//telnet port
Option telnetPort = new TypedOption<Integer>().setType(Integer.class)
.setShortName("telnet-port").setDefaultValue(DEFAULT_TELNET_PORT);
//http port
Option httpPort = new TypedOption<Integer>().setType(Integer.class)
.setShortName("http-port").setDefaultValue(DEFAULT_HTTP_PORT);
//超时时间
Option sessionTimeout = new TypedOption<Integer>().setType(Integer.class)
.setShortName("session-timeout").setDefaultValue("" + Configure.DEFAULT_SESSION_TIMEOUT_SECONDS);
//tunnel-server.jar路径
Option tunnelServer = new TypedOption<String>().setType(String.class).setShortName("tunnel-server");
//agent id
Option agentId = new TypedOption<String>().setType(String.class).setShortName("agent-id");
//statUrl
Option statUrl = new TypedOption<String>().setType(String.class).setShortName("stat-url");
CLI cli = CLIs.create("arthas").addOption(pid).addOption(core).addOption(agent).addOption(target)
.addOption(telnetPort).addOption(httpPort).addOption(sessionTimeout).addOption(tunnelServer).addOption(agentId).addOption(statUrl);
//命令行
CommandLine commandLine = cli.parse(Arrays.asList(args));
//将参数封装进Configure
Configure configure = new Configure();
configure.setJavaPid((Integer) commandLine.getOptionValue("pid"));
configure.setArthasAgent((String) commandLine.getOptionValue("agent"));
configure.setArthasCore((String) commandLine.getOptionValue("core"));
configure.setSessionTimeout((Integer)commandLine.getOptionValue("session-timeout"));
if (commandLine.getOptionValue("target-ip") == null) {
throw new IllegalStateException("as.sh is too old to support web console, " +
"please run the following command to upgrade to latest version:" +
"\ncurl -sLk https://alibaba.github.io/arthas/install.sh | sh");
}
configure.setIp((String) commandLine.getOptionValue("target-ip"));
configure.setTelnetPort((Integer) commandLine.getOptionValue("telnet-port"));
configure.setHttpPort((Integer) commandLine.getOptionValue("http-port"));
configure.setTunnelServer((String) commandLine.getOptionValue("tunnel-server"));
configure.setAgentId((String) commandLine.getOptionValue("agent-id"));
configure.setStatUrl((String) commandLine.getOptionValue("stat-url"));
return configure;
}
Arthas#attachAgent
attach到目标进程。
private void attachAgent(Configure configure) throws Exception {
VirtualMachineDescriptor virtualMachineDescriptor = null;
for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) {
String pid = descriptor.id();
if (pid.equals(Integer.toString(configure.getJavaPid()))) {
virtualMachineDescriptor = descriptor;
}
}
VirtualMachine virtualMachine = null;
try {
if (null == virtualMachineDescriptor) {
// 使用 attach(String pid) 这种方式
virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
} else {
virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
}
Properties targetSystemProperties = virtualMachine.getSystemProperties();
//判断当前版本和目标版本是否一致
String targetJavaVersion = JavaVersionUtils.javaVersionStr(targetSystemProperties);
String currentJavaVersion = JavaVersionUtils.javaVersionStr();
if (targetJavaVersion != null && currentJavaVersion != null) {
if (!targetJavaVersion.equals(currentJavaVersion)) {
AnsiLog.warn("Current VM java version: {} do not match target VM java version: {}, attach may fail.",
currentJavaVersion, targetJavaVersion);
AnsiLog.warn("Target VM JAVA_HOME is {}, arthas-boot JAVA_HOME is {}, try to set the same JAVA_HOME.",
targetSystemProperties.getProperty("java.home"), System.getProperty("java.home"));
}
}
//arthas-agent.jar path
String arthasAgentPath = configure.getArthasAgent();
//设置arthas-agent.jar path
configure.setArthasAgent(encodeArg(arthasAgentPath));
//设置arthas-core.jar path
configure.setArthasCore(encodeArg(configure.getArthasCore()));
/**
* 加载arthas-agent.jar
* 其中
* 参数1:agent路径
* 参数2:启动参数,规则:arthas-core.jar path + ";" + configure.toString()
*/
virtualMachine.loadAgent(arthasAgentPath,
configure.getArthasCore() + ";" + configure.toString());
} finally {
if (null != virtualMachine) {
//分离
virtualMachine.detach();
}
}
}
AgentBootstrap
premain
JDK1.5加入, 在项目启动时,执行main方法之前对字节码进行增强,适用于需要提前进行埋点、打桩的情况,典型如:分布式链路追踪。
agentmain
JDK1.6加入, premain不够灵活,所以在JDK1.6对其进行了补充扩展。
agentmain 可以在项目运行时,动态地对字节码进行增强,适用于需要动态地修改字节码的情况,比如:热部署,arthas。
public static void premain(String args, Instrumentation inst) {
main(args, inst);
}
public static void agentmain(String args, Instrumentation inst) {
main(args, inst);
}
AgentBootstrap#main
private static synchronized void main(String args, final Instrumentation inst) {
try {
ps.println("Arthas server agent start...");
//解码参数:传递的args参数分两个部分:agentJar路径和启动参数
args = decodeArg(args);
int index = args.indexOf(';');
String agentJar = args.substring(0, index);
final String agentArgs = args.substring(index);
//agentJar
File agentJarFile = new File(agentJar);
if (!agentJarFile.exists()) {
ps.println("Agent jar file does not exist: " + agentJarFile);
return;
}
//spyJar
File spyJarFile = new File(agentJarFile.getParentFile(), ARTHAS_SPY_JAR);
if (!spyJarFile.exists()) {
ps.println("Spy jar file does not exist: " + spyJarFile);
return;
}
//获取类加载器
final ClassLoader agentLoader = getClassLoader(inst, spyJarFile, agentJarFile);
//初始化间谍类
initSpy(agentLoader);
//绑定ip,并监听
Thread bindingThread = new Thread() {
@Override
public void run() {
try {
bind(inst, agentLoader, agentArgs);
} catch (Throwable throwable) {
throwable.printStackTrace(ps);
}
}
};
bindingThread.setName("arthas-binding-thread");
bindingThread.start();
bindingThread.join();
} catch (Throwable t) {
t.printStackTrace(ps);
try {
if (ps != System.err) {
ps.close();
}
} catch (Throwable tt) {
// ignore
}
throw new RuntimeException(t);
}
}
AgentBootstrap#getClassLoader
注意:使用了BootstrapClassLoader加载间谍类,确保间谍类被加载成功。
private static ClassLoader getClassLoader(Instrumentation inst, File spyJarFile, File agentJarFile) throws Throwable {
// 将Spy添加到BootstrapClassLoader
inst.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));
// 构造自定义的类加载器,尽量减少Arthas对现有工程的侵蚀
return loadOrDefineClassLoader(agentJarFile);
}
AgentBootstrap#initSpy
特别注意:使用BootstrapClassLoader类加载器加载了间谍类,在增强的时候,会将钩子方法的引用加载到目标类的类加载器中。
private static void initSpy(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
//加载 【AdviceWeaver】类
Class<?> adviceWeaverClass = classLoader.loadClass(ADVICEWEAVER);
//获取AdviceWeaver中钩子方法的引用, 比如:AdviceWeaver#methodOnBegin方法
Method onBefore = adviceWeaverClass.getMethod(ON_BEFORE, int.class, ClassLoader.class, String.class,
String.class, String.class, Object.class, Object[].class);
Method onReturn = adviceWeaverClass.getMethod(ON_RETURN, Object.class);
Method onThrows = adviceWeaverClass.getMethod(ON_THROWS, Throwable.class);
Method beforeInvoke = adviceWeaverClass.getMethod(BEFORE_INVOKE, int.class, String.class, String.class, String.class, int.class);
Method afterInvoke = adviceWeaverClass.getMethod(AFTER_INVOKE, int.class, String.class, String.class, String.class, int.class);
Method throwInvoke = adviceWeaverClass.getMethod(THROW_INVOKE, int.class, String.class, String.class, String.class, int.class);
Method reset = AgentBootstrap.class.getMethod(RESET);
//使用间谍类记录钩子方法的引用
Spy.initForAgentLauncher(classLoader, onBefore, onReturn, onThrows, beforeInvoke, afterInvoke, throwInvoke, reset);
}
本篇到此为止,剩下的内容下篇分析