Arthas最全使用姿势

4,767 阅读12分钟

1.前言

        由于后台应用的运行环境的差异包括容器运行参数,应用配置等等,在线上应用发生问题都是抓瞎般地排查问题,比如在查一个接口时延问题或者框架容器里加载到与预期不一样的配置参等常见问题排查场景下,通常都是把代码更新一遍到处打上log,重新打包部署,既浪费时间又浪费效率,特别是在上线发布的关键阶段尤为致命。随着技术的发展,APM链路跟踪在一定程度上解决了一些问题,但还有些稀奇古怪的问题需要进行线上debug才才能准确定位、分析并解决。 这里就需要一个比较强大调试或问题排查工具,Arthas成了Java后台应用开发排查问题的工具首选,使用Arthas已经差不多两年了,只能感叹Arthas实在好用,本篇主要是对Arthas的实现原理与一些常用场景进行讲解。

2.Aarthas介绍

Arthas(阿尔萨斯)是阿里巴巴开源的 Java 诊断工具,深受各位开发的喜爱。

3.实现原理

3.1 Java agent技术

Arthas主要是通过Java agent技术实现的,可理解为一个有插件作用的jar包, 这个jar包通过**JVMTI (JVM Tool Interface)完成加载,最终借助JPLISAgent(Java Programming Language Instrumentation Services Agent)**完成对目标代码的修改。

Java agent技术的主要功能如下:

  • 在加载java文件之前做拦截修改字节码

  • 在运行期将已经加载的类的修改字节码

  • 获取所有已经被加载过的类

  • 获取所有已经被初始化过后的类

  • 获取某个对象的大小

  • 将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载

  • 将某个jar加入到classpath里供AppClassloard去加载

  • 设置某些native方法的前缀,主要在查找native方法的时候做规则匹配

这里实现了一个简单的Java agent的实现如下:

public class AgentApplication {
    public static void premain(String arg, Instrumentation instrumentation) {
        instrumentation.addTransformer(new DumpClassesService());
    }
}

public class DumpClassesService implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, 
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
            byte[] classfileBuffer){ 
         CtClass cl = null;        
         try {            
                ClassPool classPool = ClassPool.getDefault();            
                cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
                for (CtMethod method : cl.getDeclaredMethods()) {
                //插入代码
                method.addLocalVariable("start", CtClass.longType);                
                method.insertBefore("start = System.currentTimeMillis();");    
                String methodName = method.getLongName();
                //插入代码
                method.insertAfter("System.out.println(\"" + 
                        methodName + " cost: \" + (System" +"
                        .currentTimeMillis() - start));");
                return cl.toBytecode();
         } catch (Exception e) {            
            e.printStackTrace();        
         }
    }
}

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
        <manifestEntries>
            <Premain-Class>com.demo.AgentApplication</Premain-Class>
            <Agent-Class>com.demo.AgentApplication</Agent-Class>
            <Can-Redefine-Classes>true</Can-Redefine-Classes>
            <Can-Retransform-Classes>true</Can-Retransform-Classes>
            </manifestEntries>
             </archive>
    </configuration>
</plugin>

  • 启动时加载: 启动参数增加-javaagent:[path],其中path为对应的agent的jar包路径

    java -javaagent:/java-agent.jar hello

  • 运行中加载: 使用com.sun.tools.attach.VirtualMachine加载,

    String jvmPid = [pid]; VirtualMachine jvm = VirtualMachine.attach(jvmPid); jvm.loadAgent([agent的jar包路径]); jvm.detach();

通过Java agent技术进行类的字节码修改最主要使用的就是Java Instrumentation API。它需要依赖JVMTIAttach API机制实现。目前Instrument类已支持了在运行时对类定义的修改。如上所示,要使用Instrument的类修改功能,我们需要实现它提供的ClassFileTransformer接口,定义一个类文件转换器。接口中的transform()方法会在类文件被加载时调用,而在transform方法里,可以利用ASM或Javassist等工具对传入的字节码进行改写或替换,生成新的字节码后返回。

/**
 * 对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。
 * retransformation可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性
 */
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
/**
 * 获取一个对象的大小
 */
long getObjectSize(Object objectToSize);
/**
 * 将一个jar加入到bootstrap classloader的classpath里
 */
void appendToBootstrapClassLoaderSearch(JarFile jarfile);
/**
 * 获取当前被JVM加载的所有类对象
 */
Class[] getAllLoadedClasses();

3.2 Arthas主要组成结构

       Arthas整个项目的代码并不是很多,而且结构也比较清晰易懂。下面是Arthas的主要组件以及相互之间的联系。

主要有以下几大组件:

  • arthas-core.jar是服务器端的启动入口类,调用VirtualMachine#attach到目标进程,并加载arthas-agent.jar作为agent包。
  •  arthas-agent.jar既可以使用premain方式(在目标进程启动之前,通过-agent参数静态指定),也可以通过agentmain方式(在进程启动之后attach上去)。arthas-agent会使用自定义的classloader(ArthasClassLoader)加载arthas-core.jar里面的Configure类以及ArthasBootstrap。 同时程序运行的时候会使用arthas-spy.jar。 
  • arthas-spy.jar里面只包含Spy类,目的是为了将Spy类使用BootstrapClassLoader来加载,从而使目标进程的java应用可以访问Spy类。通过ASM修改字节码,可以将Spy类的方法ON_BEFORE_METHOD, ON_RETURN_METHOD等编织到目标类里面。
  • arthas-client.jar是客户端程序,用来连接arthas-core.jar启动的服务端代码,使用telnet方式。一般由arthas-boot.jar和as.sh来负责启动。

3.3 初始化主流程

        Arthas有两种启动方式,一种是作为agent插件,另一种是直接通过Arthas类启动。第一种是在maven打包中指定了Arthas agent的启动类,在java -agent启动后会去调用AgentBootStrap#main方法进行初始化。

<archive>
    <manifestEntries>
        <Premain-Class>com.taobao.arthas.agent.AgentBootstrap</Premain-Class>
        <Agent-Class>com.taobao.arthas.agent.AgentBootstrap</Agent-Class>
        <Can-Redefine-Classes>true</Can-Redefine-Classes>
        <Can-Retransform-Classes>true</Can-Retransform-Classes>
    </manifestEntries>
</archive>

       在AgentBootStrap#main中主要是初始化Advice增强时使用到的钩子对象, 并把Spy类加载到BootstrapClassLoader中。

private static synchronized void main(final String args, final Instrumentation inst) {
    try {
        ...
        //获取ClassLoader对象
        final ClassLoader agentLoader = getClassLoader(inst, spyJarFile, agentJarFile);
        //初始化钩子对象
        initSpy(agentLoader);

        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) {
        ...
    }
}

private static ClassLoader getClassLoader(Instrumentation inst, File spyJarFile, File agentJarFile) throws Throwable {
    // 将Spy添加到BootstrapClassLoader
    inst.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));

    // 构造自定义的类加载器,尽量减少Arthas对现有工程的侵蚀
    return loadOrDefineClassLoader(agentJarFile);
}

      Spy类中有各种Advice钩子引用静态Method对象,这些静态Method对象会在字节增强时使用到。

private static void initSpy(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
        Class<?> adviceWeaverClass = classLoader.loadClass(ADVICEWEAVER);
        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);
        Method afterInvoke = adviceWeaverClass.getMethod(AFTER_INVOKE, int.class, String.class, String.class, String.class);
        Method throwInvoke = adviceWeaverClass.getMethod(THROW_INVOKE, int.class, String.class, String.class, String.class);
        Method reset = AgentBootstrap.class.getMethod(RESET);
        Spy.initForAgentLauncher(classLoader, onBefore, onReturn, onThrows, beforeInvoke, afterInvoke, throwInvoke, reset);
}

       在AgentBootStrap#bind中,会去加载ArthasBootStrap类,并调用bind方法,这里会去启动服务器端。

private static void bind(Instrumentation inst, ClassLoader agentLoader, String args) throws Throwable {
    Class<?> classOfConfigure = agentLoader.loadClass(ARTHAS_CONFIGURE);
    Object configure = classOfConfigure.getMethod(TO_CONFIGURE, String.class).invoke(null, args);
    int javaPid = (Integer) classOfConfigure.getMethod(GET_JAVA_PID).invoke(configure);
    Class<?> bootstrapClass = agentLoader.loadClass(ARTHAS_BOOTSTRAP);
    Object bootstrap = bootstrapClass.getMethod(GET_INSTANCE, int.class, Instrumentation.class).invoke(null, javaPid, inst);
    boolean isBind = (Boolean) bootstrapClass.getMethod(IS_BIND).invoke(bootstrap);
}

       另一种是直接使用Arthas类启动,首先遍历所有的进程,并通过VirtualMachine类将agent类挂在到目标进程上,并进行agent的初始化。

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 {
             // 使用 attach(String pid) 这种方式
            if (null == virtualMachineDescriptor) {
                virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
            } else {
                virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
            }

            Properties targetSystemProperties = virtualMachine.getSystemProperties();
            String targetJavaVersion = targetSystemProperties.getProperty("java.specification.version");
            String currentJavaVersion = System.getProperty("java.specification.version");
             ...
            virtualMachine.loadAgent(configure.getArthasAgent(),
                            configure.getArthasCore() + ";" + 
                            configure.toString());
        } finally {
            if (null != virtualMachine) {
                virtualMachine.detach();
            }
        }
}

3.4 通信主要流程

       Arthas应用是基于C/S的通信架构来设计的,支持Telnet和Http的客户端协议通信,服务器端接收到客户端连接后都会为每个连接生成会话窗口,并会将发过来的请求内容解析后生成命令交由任务控制去完成响应。其中每个客户端通信都对应唯一的ShellImpl实现,里面包括了唯一的Session实例,并持有JobControllerImpl和InternalCommandManager对象用于组装出异步任务去执行这个命令。

         在服务器端的启动过程中会调用ArthasBootstrap#bind中, 会启动Telnet和Http通信协议的服务器实例并接收请求。InternalCommandManager类记录了所有命令,通过名字可搜索到对应的命令实现类,这里Command类会被包装AnnotatedCommand类放入列表中。

public class ArthasBootstrap {  public void bind(Configure configure) throws Throwable {
    try {
        shellServer = new ShellServerImpl(options, this);

        BuiltinCommandPack builtinCommands = new BuiltinCommandPack();        
        List<CommandResolver> resolvers = new ArrayList<CommandResolver>();       
        resolvers.add(builtinCommands);

        //初始化Telnet协议服务器端
        shellServer.registerTermServer(new TelnetTermServer(               
             configure.getIp(), configure.getTelnetPort(), 
                options.getConnectionTimeout()));
        //初始化http协议服务端
        shellServer.registerTermServer(new HttpTermServer(                
            configure.getIp(), configure.getHttpPort(),
                 options.getConnectionTimeout()));        
        //服务器注册命令解析器实例
        for (CommandResolver resolver : resolvers) {           
             shellServer.registerCommandResolver(resolver);    
        }        
        ...   
     } catch (Throwable e) {
        ...
    }
  }
}


public class BuiltinCommandPack implements CommandResolver {
    ...
    //将所有支持的命令放入列表中
    private static void initCommands() {
        //包装成了AnnotatedCommandImpl类,加入列表中
        commands.add(Command.create(HelpCommand.class));
        commands.add(Command.create(KeymapCommand.class));
    }
}
public class InternalCommandManager {
    private final List<CommandResolver> resolvers;
   //根据已有命令列表查找出命令实现类
    private static Command getCommand(CommandResolver commandResolver, String name) {
         List<Command> commands = commandResolver.commands();
        if (commands == null || commands.isEmpty()) 
            return null;       
        }
        for (Command command : commands) {
            if (name.equals(command.name())) {
                return command;
        }
    }
    return null;
}

        其中客户端的每个请求都是一个异步任务的形式进行响应的,以TelnetTermServer的请求-响应过程为例,Server中持有一个JobController的实现实例对响应进行任务化处理,这里不展开赘叙,在JobControllerImpl会生成命令处理对象,调用命令的具体方法。

public interface JobController {
    //获取所有的任务
    Set<Job> jobs();
    //根据id获取任务信息
    Job getJob(int id);
    //根据命令管理器与Shell显示实现创建任务
    Job createJob(InternalCommandManager commandManager, 
                List<CliToken> tokens, ShellImpl shell);
    //任务后置处理器
    void close(Handler<Void> completionHandler);
    //任务关闭
    void close();
}

public class JobControllerImpl implements JobController {    @Override
    public Job createJob(InternalCommandManager commandManager, 
                List<CliToken> tokens, ShellImpl shell) {
        //以递增的方式生成任务id
        int jobId = idGenerator.incrementAndGet();
        ...
        //生成Process对象
        Process process = createProcess(tokens, commandManager, jobId, shell.term());
        process.setJobId(jobId);
        JobImpl job = new JobImpl(jobId, this, process, line.toString(), 
                runInBackground, shell);
        //放入映射表中
        jobs.put(jobId, job);
        return job;
}

private Process createProcess(List<CliToken> line, 
            InternalCommandManager commandManager, int jobId, Term term) {
    try {
        ListIterator<CliToken> tokens = line.listIterator();
        while (tokens.hasNext()) {
            CliToken token = tokens.next();
            if (token.isText()) {
                //从命令列表中获取对应命令
                Command command = commandManager.getCommand(token.value());
                ...
            }
        }
        ...
    } catch (Exception e) {
        ...
    }
}

private Process createCommandProcess(Command command, 
                    ListIterator<CliToken> tokens, int jobId, Term term) {
    ....
    ProcessOutput ProcessOutput = new ProcessOutput(stdoutHandlerChain, cacheLocation, term);
    return new ProcessImpl(command, remaining, command.processHandler(), ProcessOutput);
}

3.5 字节码增强原理

可以从源码中可以看到从控制端输入的各种命令都会通过telenet客户端编码成通信协议后发送到服务器端,并在服务器端解析成各种命令,比如常见的trace/watch等命令,在这些命令都会带上具体的类#方法来进行监控,并将监控结果返回到客户端显示,这里做监控统计的实现方式类似于AOP实现,会在Enhance类对目标类或对象进行增强,来进行统计并展示结果。

         Enhance中的主要逻辑如下所示,该类继承了ClassFileTransformr类,主要有两种情况会去做增强,第一种是在做类加载时会调用transform进行增强,另一种是接收到命令后调用enhance方法对目标类进行增强。增强使用的ASM框架中的ClassReader/ClassWriter类去做类的增强,具体的增强逻辑在AdviceWeave类中。

public class Enhancer implements ClassFileTransformer {
    
    ... 
    @Override
    public byte[] transform(...) {
        final ClassReader cr;
        // 首先先检查是否在缓存中存在Class字节码
        final byte[] byteOfClassInCache = classBytesCache.get(classBeingRedefined);
        if (null != byteOfClassInCache) {
            cr = new ClassReader(byteOfClassInCache);
        } else {
            //如果没在缓存中则从原始字节码增强
            cr = new ClassReader(classfileBuffer);
        }

        // 字节码增强
        final ClassWriter cw = 
            new ClassWriter(cr, COMPUTE_FRAMES | COMPUTE_MAXS) {....};

        try {
            // 生成增强字节码
            cr.accept(new AdviceWeaver(adviceId, isTracing, skipJDKTrace, cr.getClassName(), methodNameMatcher, affect, cw), EXPAND_FRAMES);
            final byte[] enhanceClassByteArray = cw.toByteArray();
            // 生成成功,推入缓存
            classBytesCache.put(classBeingRedefined, enhanceClassByteArray);
            ....
            try {
                spy(inClassLoader);
            } catch (Throwable t) {
                ...
            }
            return enhanceClassByteArray;
        } catch (Throwable t) {
            ...
        }
        return null;
    }

    public static synchronized EnhancerAffect enhance(...) {
        ....
        // 构建增强器
        final Enhancer enhancer = new Enhancer(adviceId, isTracing, skipJDKTrace,
                                 enhanceClassSet, methodNameMatcher, affect);
        try {
            ...
            //Instrument中添加类转换器
            inst.addTransformer(enhancer, true);
            // 批量增强
            if (GlobalOptions.isBatchReTransform) {
                final int size = enhanceClassSet.size();
                final Class<?>[] classArray = new Class<?>[size];
                arraycopy(enhanceClassSet.toArray(), 0, classArray, 0, size);
                if (classArray.length > 0) {
                //转换class定义,会再调用Enhance#tranformClass方法
                inst.retransformClasses(classArray);
            }
            } else {
                for (Class<?> clazz : enhanceClassSet) {
                    try {
                        //转换class定义,会再调用Enhance#tranformClass方法
                        inst.retransformClasses(clazz);
                        ...
                    } catch (Throwable t) {
                        ....
                }
            }
        } catch(Exception ex) {
            ...
        } finally {
            //从Instrument中去掉类转换器
            inst.removeTransformer(enhancer);
        }
        return affect;
    }
}

    这里的增强做法跟AOP非常类似,都是使用ClassVisitor,然后将前后置方法插入到目标方法体内,当请求时会触发监听器的方法。

public class AdviceWeaver extends ClassVisitor implements Opcodes {

    ...
    // 通知监听器集合
    private final static Map<Integer/*ADVICE_ID*/, AdviceListener> advices
            = new ConcurrentHashMap<Integer, AdviceListener>();
    ... 

    public static void methodOnBegin(
            int adviceId,
            ClassLoader loader, String className, String methodName, String methodDesc,
            Object target, Object[] args) {
        try {
             ...
            final AdviceListener listener = getListener(adviceId);
            ...
            // 获取通知器并做前置通知
            before(listener, loader, className, methodName, methodDesc, target, args);
            ...
        } finally {
            ...
        }
    }

    @Override    
    public MethodVisitor visitMethod(...) {

        final MethodVisitor mv = 
            super.visitMethod(access, name, desc, signature, exceptions);        
         ....  
       return new AdviceAdapter(ASM5, new JSRInlinerAdapter(mv, access, name, desc, signature, exceptions), access, name, desc) {   
       private final Type ASM_TYPE_SPY = Type.getType("Ljava/arthas/Spy;");   
         .....
           @Override           
           protected void onMethodEnter() 
           {            
                codeLockForTracing.lock(new CodeLock.Block() {       
                 @Override            
                 public void code() {                
                    final StringBuilder append = new StringBuilder();      
                    _debug(append, "debug:onMethodEnter()");            
                    // 加载before方法                
                    loadAdviceMethod(KEY_ARTHAS_ADVICE_BEFORE_METHOD);
                    _debug(append, "debug:onMethodEnter() > loadAdviceMethod()");  
                    // 推入Method.invoke()的第一个参数               
                     pushNull();               
                    // 方法参数
                    loadArrayForBefore();                
                    _debug(append, "debug:onMethodEnter() > loadAdviceMethod    
                        > loadArrayForBefore()");      
                  // 调用方法               
                     invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE);   
                     pop();               
                     _debug(append, "debug:onMethodEnter() > 
                        loadAdviceMethod() > loadArrayForBefore() > invokeVirtual()");         
   }        });     mark(beginLabel);    }
}
    

4.业务常用场景

4.1 查看实现

sc *Wrapper*
...
jad org.apache.dubbo.qos.protocol.QosProtocolWrapper
jad org.apache.dubbo.registry.ListenerRegistryWrapper
jad org.apache.dubbo.registry.RegistryFactoryWrapper

sc *Adaptive*
...
sc -d org.apache.dubbo.rpc.protocol.InvokerWrapper  查看类的完整信息
 

4.2 调用记录与重放

tt -t com.demo.provider.UserServiceImpl saveUser

...
 INDEX     TIMESTAMP               COST(ms)    IS-RET    IS-EXP   OBJECT            CLASS                                METHOD
------------------------------------------------------------------------------------------------------------------------------------------------------------
 1000      2021-03-28 16:33:58     0.183831    false     true     0x1b936e8         UserServiceImpl                      saveUser


//重放记录到的结果
tt --play -i 1000

...
 RE-INDEX         1000
 GMT-REPLAY       2021-03-28 16:34:57
 OBJECT           0x1b936e8
 CLASS            com.seewo.demo.provider.UserServiceImpl
 METHOD           saveUser
 PARAMETERS[0]    @String[test]
 PARAMETERS[1]    @Integer[18]
 IS-RETURN        false
 IS-EXCEPTION     true
 THROW-EXCEPTION  java.lang.RuntimeException: test

4.3 日志级别动态调整

ognl '@org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter@logger.logger'
@Log4jLogger[
    FQCN=@String[org.apache.dubbo.common.logger.support.FailsafeLogger],
    logger=@Logger[org.apache.log4j.Logger@7b89497f],
]


sc -d org.apache.log4j.Logger

...
 class-info        org.apache.log4j.Logger
 code-source       /Users/martin/.m2/repository/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar
 name              org.apache.log4j.Logger
 isInterface       false
 isAnnotation      false
 isEnum            false


ognl '@org.slf4j.LoggerFactory@getLogger("root").getLevel().toString()'


logger --name [ROOT] --level [error] -c [classLoader的hashCode]

4.4 获取Spring上下文信息

4.4.1 基于SpringMVC

tt -t org.springframework.web.servlet.mvc.method.annotation
        .RequestMappingHandlerAdapter invokeHandlerMethod -n 3


//一定要去请求不然是记录不到的
...
 INDEX      TIMESTAMP                    COST(ms)      IS-RET     IS-EXP      OBJECT               CLASS                                     METHOD
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 1000       2021-04-10 08:15:20          911.495905    true       false       0x5f50206c           RequestMappingHandlerAdapter              invokeHandlerMethod



//这里的1000是上面的结果索引
tt -i 1000 -w 'target.getApplicationContext()'

//获取到具体对象后调用方法,最后加上入参与出参
tt -i 1000 -w 'target.getApplicationContext().getBeanFactory()
.singletonObjects.get("agoalEpassDataPanelFacadeImpl")
.getEmployeeInfoByWorkNos({"xxxxxxx"})' '{params, returnObj}' -x 3

...
@AnnotationConfigServletWebServerApplicationContext[
    reader=@AnnotatedBeanDefinitionReader[org.springframework.context.annotation.AnnotatedBeanDefinitionReader@522b017d],
    scanner=@ClassPathBeanDefinitionScanner[org.springframework.context.annotation.ClassPathBeanDefinitionScanner@2f18e9bb],
    annotatedClasses=@LinkedHashSet[isEmpty=true;size=0],
    basePackages=null,
    logger=@Slf4jLocationAwareLog[org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog@7dea2bff],
    DISPATCHER_SERV

4.4.2 基于Dubbo

sc -d 'org.apache.dubbo.config.spring.extension.SpringExtensionFactory'


ognl -c 5197848c '#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next'ognl -c 5197848c '#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next, #context.getBean("tianqiRedisFactory").jedisPools' -x 3

4.5 查看抛出异常

watch com.seewo.demo.provider.UserServiceImpl * -e -x 2 '{params,throwExp}'


watch com.seewo.demo.provider.UserServiceImpl * "{params,returnObj}" -x 2

...
method=com.seewo.demo.provider.UserServiceImpl.saveUser location=AtExceptionExit
ts=2021-03-28 16:17:50; [cost=0.599027ms] result=@ArrayList[
    @Object[][
        @String[test],
        @Integer[18],
    ],
    java.lang.RuntimeException: test

watch com.seewo.demo.provider.UserServiceImpl -x 2 '{params,returnObj}'

4.6 查看调用链路

trace com.seewo.demo.provider.UserServiceImpl run "#cost > 10"

...

trace -E com.seewo.demo.provider.UserServiceImpl method1|method2|method3

.....

 4.7 线上调试代码

redefine /**/UserServiceImpl.class

...
redefine success, size: 1, classes:
com.demo.provider.UserServiceImpl

4.8 查看Spring容器运行配置项

tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod


tt -i 1000 -w 'target.getApplicationContext()'


 tt -i 1000 -w 'target.getApplicationContext().getBean("tppApi").tppSearchSortAppId'

5.总结

     本篇是对经常使用的问题排查工具Arthas的全面讲解,包括了实现技术原理与常用的场景,通过对Java agent与ASM等技术原理了解,基本上搞懂了Arthas应用的运作原理,其中对JVM的开放API使用与ASM字节码增强的逻辑代码写的非常棒,很有学习的价值!

参考文献

blog.csdn.net/wangzhongsh… 一个简单的java ageng实现

blog.csdn.net/can007/arti… java代码的编译、执行

www.jianshu.com/p/63c328ca2… java agent实现

www.jianshu.com/p/4e34d0ab4… Arthas实现原理

developer.aliyun.com/article/675… 当Dubbo遇上Arthas-排查问题的实践