<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手把手教你實(shí)現(xiàn)一個(gè)Java Agent

          共 55910字,需瀏覽 112分鐘

           ·

          2024-07-27 13:32

          來源:juejin.cn/post/7215401973843034171

          ?? 歡迎加入小哈的星球,你將獲得: 專屬的項(xiàng)目實(shí)戰(zhàn) / 1v1 提問 / Java 學(xué)習(xí)路線 / 學(xué)習(xí)打卡 / 每月贈書 / 社群討論

          • 新項(xiàng)目:《從零手?jǐn)]:仿小紅書(微服務(wù)架構(gòu))》 正在持續(xù)爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 點(diǎn)擊查看項(xiàng)目介紹
          • 《從零手?jǐn)]:前后端分離博客項(xiàng)目(全棧開發(fā))》 2期已完結(jié),演示鏈接:http://116.62.199.48/;

          截止目前,累計(jì)輸出 50w+ 字,講解圖 2200+ 張,還在持續(xù)爆肝中.. 后續(xù)還會上新更多項(xiàng)目,目標(biāo)是將 Java 領(lǐng)域典型的項(xiàng)目都整一波,如秒殺系統(tǒng), 在線商城, IM 即時(shí)通訊,Spring Cloud Alibaba 等等,戳我加入學(xué)習(xí),解鎖全部項(xiàng)目,已有1800+小伙伴加入

          • 故事的小黃花
          • Instrumentation
          • Agent
          • Attach
          • Arthas
          • 搭建調(diào)試環(huán)境
          • bytekit
          • trace
          • 參考資料

          故事的小黃花

          團(tuán)隊(duì)中有同事在做性能優(yōu)化相關(guān)的工作,因?yàn)楣净A(chǔ)設(shè)施不足,同事在代碼中寫了大量的代碼統(tǒng)計(jì)某個(gè)方法的耗時(shí),大概的代碼形式就是

          @Override
          public void method(Req req) {
              StopWatch stopWatch = new StopWatch();
              stopWatch.start("某某方法-耗時(shí)統(tǒng)計(jì)");
              method()
              stopWatch.stop();
              log.info("查詢耗時(shí)分布:{}", stopWatch.prettyPrint());
          }

          這樣的代碼非常多,侵入性很大,聯(lián)想到之前學(xué)習(xí)的Java Agent技術(shù),可以無侵入式地解決這類問題,所以做了一個(gè)很小很小的demo

          Instrumentation

          在了解Agent之前需要先看看Instrumentation

          JDK從1.5版本開始引入了java.lang.instrument包,該包提供了一些工具幫助開發(fā)人員實(shí)現(xiàn)字節(jié)碼增強(qiáng),Instrumentation接口的常用方法如下

          public interface Instrumentation {
              /**
               * 注冊Class文件轉(zhuǎn)換器,轉(zhuǎn)換器用于改變Class文件二進(jìn)制流的數(shù)據(jù)
               *
               * @param transformer          注冊的轉(zhuǎn)換器
               * @param canRetransform       設(shè)置是否允許重新轉(zhuǎn)換
               */
              void addTransformer(ClassFileTransformer transformer, boolean canRetransform);

              /**
               * 移除一個(gè)轉(zhuǎn)換器
               *
               * @param transformer          需要移除的轉(zhuǎn)換器
               */
              boolean removeTransformer(ClassFileTransformer transformer);
            
              /**
               * 在類加載之后,重新轉(zhuǎn)換類,如果重新轉(zhuǎn)換的方法有活躍的棧幀,那些活躍的棧幀繼續(xù)運(yùn)行未轉(zhuǎn)換前的方法
               *
               * @param 重新轉(zhuǎn)換的類數(shù)組
               */
              void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
            
              /**
               * 當(dāng)前JVM配置是否支持重新轉(zhuǎn)換
               */
              boolean isRetransformClassesSupported();

              /**
               * 獲取所有已加載的類
               */
              @SuppressWarnings("rawtypes")
              Class[] getAllLoadedClasses();
          }
          public interface ClassFileTransformer {
              // className參數(shù)表示當(dāng)前加載類的類名,classfileBuffer參數(shù)是待加載類文件的字節(jié)數(shù)組
              // 調(diào)用addTransformer注冊ClassFileTransformer以后,后續(xù)所有JVM加載類都會被它的transform方法攔截
              // 這個(gè)方法接收原類文件的字節(jié)數(shù)組,在這個(gè)方法中做類文件改寫,最后返回轉(zhuǎn)換過的字節(jié)數(shù)組,由JVM加載這個(gè)修改過的類文件
              // 如果transform方法返回null,表示不對此類做處理,如果返回值不為null,JVM會用返回的字節(jié)數(shù)組替換原來類的字節(jié)數(shù)組
              byte[] transform(  ClassLoader         loader,
                          String              className,
                          Class<?>            classBeingRedefined,
                          ProtectionDomain    protectionDomain,
                          byte[]              classfileBuffer)
                  throws IllegalClassFormatException;
          }

          Instrumentation有兩種使用方式

          1. 在JVM啟動(dòng)的時(shí)候添加一個(gè)Agent jar包
          2. JVM運(yùn)行以后在任意時(shí)刻通過Attach API遠(yuǎn)程加載Agent的jar包

          Agent

          使用Java Agent需要借助一個(gè)方法,該方法的方法簽名如下

          public static void premain (String agentArgs, Instrumentation instrumentation) {
          }

          從字面上理解,就是運(yùn)行在main()函數(shù)之前的類。在Java虛擬機(jī)啟動(dòng)時(shí),在執(zhí)行main()函數(shù)之前,會先運(yùn)行指定類的premain()方法,在premain()方法中對class文件進(jìn)行修改,它有兩個(gè)入?yún)?/p>

          1. agentArgs:啟動(dòng)參數(shù),在JVM啟動(dòng)時(shí)指定
          2. instrumentation:上文所將的Instrumentation的實(shí)例,我們可以在方法中調(diào)用上文所講的方法,注冊對應(yīng)的Class轉(zhuǎn)換器,對Class文件進(jìn)行修改

          如下圖,借助Instrumentation,JVM啟動(dòng)時(shí)的處理流程是這樣的:JVM會執(zhí)行指定類的premain()方法,在premain()中可以調(diào)用Instrumentation對象的addTransformer方法注冊ClassFileTransformer。當(dāng)JVM加載類時(shí)會將類文件的字節(jié)數(shù)組傳遞給ClassFileTransformer的transform方法,在transform方法中對Class文件進(jìn)行解析和修改,之后JVM就會加載轉(zhuǎn)換后的Class文件

          JVM啟動(dòng)時(shí)的處理流程

          那我們需要做的就是寫一個(gè)轉(zhuǎn)換Class文件的ClassFileTransformer,下面用一個(gè)計(jì)算函數(shù)耗時(shí)的小例子看看Java Agent是怎么使用的

          public class MyClassFileTransformer implements ClassFileTransformer {
              @Override
              public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
                  if ("com/example/aop/agent/MyTest".equals(className)) {
                      // 使用ASM框架進(jìn)行字節(jié)碼轉(zhuǎn)換
                      ClassReader cr = new ClassReader(classfileBuffer);
                      ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
                      ClassVisitor cv = new TimeStatisticsVisitor(Opcodes.ASM7, cw);
                      cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
                      return cw.toByteArray();
                  }
                  return classfileBuffer;

              }
          }
          public class TimeStatisticsVisitor extends ClassVisitor {

              public TimeStatisticsVisitor(int api, ClassVisitor classVisitor) {
                  super(Opcodes.ASM7, classVisitor);
              }

              @Override
              public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                  MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
                  if (name.equals("<init>")) {
                      return mv;
                  }
                  return new TimeStatisticsAdapter(api, mv, access, name, descriptor);
              }
          }
          public class TimeStatisticsAdapter extends AdviceAdapter {

              protected TimeStatisticsAdapter(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
                  super(api, methodVisitor, access, name, descriptor);
              }

              @Override
              protected void onMethodEnter() {
                  // 進(jìn)入函數(shù)時(shí)調(diào)用TimeStatistics的靜態(tài)方法start
                  super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/aop/agent/TimeStatistics""start""()V"false);
                  super.onMethodEnter();
              }

              @Override
              protected void onMethodExit(int opcode) {
                  // 退出函數(shù)時(shí)調(diào)用TimeStatistics的靜態(tài)方法end
                  super.onMethodExit(opcode);
                  super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/aop/agent/TimeStatistics""end""()V"false);
              }
          }

          public class TimeStatistics {
              public static ThreadLocal<Long> t = new ThreadLocal<>();

              public static void start() {
                  t.set(System.currentTimeMillis());
              }
              public static void end() {
                  long time = System.currentTimeMillis() - t.get();
                  System.out.println(Thread.currentThread().getStackTrace()[2] + " spend: " + time);
              }
          }
          public class AgentMain {
              // premain()函數(shù)中注冊MyClassFileTransformer轉(zhuǎn)換器
              public static void premain (String agentArgs, Instrumentation instrumentation) {
                  System.out.println("premain方法");
                  instrumentation.addTransformer(new MyClassFileTransformer(), true);
              }
          }
          <build>
            <plugins>
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.1.1</version>
                <configuration>
                  <descriptorRefs>
                    <!--將應(yīng)用的所有依賴包都打到j(luò)ar包中。如果依賴的是 jar 包,jar 包會被解壓開,平鋪到最終的 uber-jar 里去。輸出格式為 jar-->
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                  </descriptorRefs>
                  <archive>
                    <manifestEntries>
                      // 指定premain()的所在方法
                      <Agent-CLass>com.example.aop.agent.AgentMain</Agent-CLass>
                      <Premain-Class>com.example.aop.agent.AgentMain</Premain-Class>
                      <Can-Redefine-Classes>true</Can-Redefine-Classes>
                      <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                  </archive>
                </configuration>
                <executions>
                  <execution>
                    <phase>package</phase>
                    <goals>
                      <goal>single</goal>
                    </goals>
                  </execution>
                </executions>
              </plugin>
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                  <source>${maven.compiler.source}</source>
                  <target>${maven.compiler.target}</target>
                </configuration>
              </plugin>
            </plugins>
          </build>

          使用命令行執(zhí)行下面的測試類

          java -javaagent:/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.example.aop.agent.MyTest
          public class MyTest {
              public static void main(String[] args) throws InterruptedException {
                  Thread.sleep(3000);
              }
          }

          計(jì)算出了某個(gè)方法的耗時(shí)

          計(jì)算出某個(gè)方法的耗時(shí)

          Attach

          在上面的例子中,我們只能在JVM啟動(dòng)時(shí)指定一個(gè)Agent,這種方式局限在main()方法執(zhí)行前,如果我們想在項(xiàng)目啟動(dòng)后隨時(shí)隨地地修改Class文件,要怎么辦呢?這個(gè)時(shí)候需要借助Java Agent的另外一個(gè)方法,該方法的簽名如下

          public static void agentmain (String agentArgs, Instrumentation inst) {
          }

          agentmain()的參數(shù)與premain()有著同樣的含義,但是agentmain()是在Java Agent被Attach到Java虛擬機(jī)上時(shí)執(zhí)行的,當(dāng)Java Agent被attach到Java虛擬機(jī)上,Java程序的main()函數(shù)一般已經(jīng)啟動(dòng),并且程序很可能已經(jīng)運(yùn)行了相當(dāng)長的時(shí)間,此時(shí)通過Instrumentation.retransformClasses()方法,可以動(dòng)態(tài)轉(zhuǎn)換Class文件并使之生效,下面用一個(gè)小例子演示一下這個(gè)功能

          下面的類啟動(dòng)后,會不斷打印出100這個(gè)數(shù)字,我們通過Attach功能使之打印出50這個(gè)數(shù)字

          public class PrintNumTest {
              public static void main(String[] args) throws InterruptedException {
                  while (true) {
                      System.out.println(getNum());
                      Thread.sleep(3000);
                  }
              }
              private static int getNum() {
                  return 100;
              }
          }

          依然是定義一個(gè)ClassFileTransformer,使用ASM框架修改getNum()方法

          public class PrintNumTransformer implements ClassFileTransformer {
              @Override
              public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                  if ("com/example/aop/agent/PrintNumTest".equals(className)) {
                      System.out.println("asm");
                      ClassReader cr = new ClassReader(classfileBuffer);
                      ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
                      ClassVisitor cv = new TransformPrintNumVisitor(Opcodes.ASM7, cw);
                      cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
                      return cw.toByteArray();
                  }
                  return classfileBuffer;
              }
          }
          public class TransformPrintNumVisitor extends ClassVisitor {
              public TransformPrintNumVisitor(int api, ClassVisitor classVisitor) {
                  super(Opcodes.ASM7, classVisitor);
              }
              @Override
              public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                  MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
                  if (name.equals("getNum")) {
                      return new TransformPrintNumAdapter(api, mv, access, name, descriptor);
                  }
                  return mv;
              }

          }

          public class TransformPrintNumAdapter extends AdviceAdapter {

              protected TransformPrintNumAdapter(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
                  super(api, methodVisitor, access, name, descriptor);
              }

              @Override
              protected void onMethodEnter() {
                  super.visitIntInsn(BIPUSH, 50);
                  super.visitInsn(IRETURN);
              }
          }
          public class PrintNumAgent {

              public static void agentmain (String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
                  System.out.println("agentmain");
                  inst.addTransformer(new PrintNumTransformer(), true);

                  Class[] allLoadedClasses = inst.getAllLoadedClasses();
                  for (Class allLoadedClass : allLoadedClasses) {
                      if (allLoadedClass.getSimpleName().equals("PrintNumTest")) {
                          System.out.println("Reloading: " + allLoadedClass.getName());
                          inst.retransformClasses(allLoadedClass);
                          break;
                      }
                  }
              }
          }
          <build>
            <plugins>
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.1.1</version>
                <configuration>
                  <descriptorRefs>
                    <!--將應(yīng)用的所有依賴包都打到j(luò)ar包中。如果依賴的是 jar 包,jar 包會被解壓開,平鋪到最終的 uber-jar 里去。輸出格式為 jar-->
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                  </descriptorRefs>
                  <archive>
                    <manifestEntries>
                      // 指定agentmain所在的類
                      <Agent-CLass>com.example.aop.agent.PrintNumAgent</Agent-CLass>
                      <Premain-Class>com.example.aop.agent.PrintNumAgent</Premain-Class>
                      <Can-Redefine-Classes>true</Can-Redefine-Classes>
                      <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                  </archive>
                </configuration>
                <executions>
                  <execution>
                    <phase>package</phase>
                    <goals>
                      <goal>single</goal>
                    </goals>
                  </execution>
                </executions>
              </plugin>
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                  <source>${maven.compiler.source}</source>
                  <target>${maven.compiler.target}</target>
                </configuration>
              </plugin>
            </plugins>
          </build>

          因?yàn)槭强邕M(jìn)程通信,Attach的發(fā)起端是一個(gè)獨(dú)立的java程序,這個(gè)java程序會調(diào)用VirtualMachine.attach方法開始合目標(biāo)JVM進(jìn)行跨進(jìn)程通信

          public class MyAttachMain {
              public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
                  VirtualMachine virtualMachine = VirtualMachine.attach(args[0]);
                  try {
                      virtualMachine.loadAgent("/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar");
                  } finally {
                      virtualMachine.detach();
                  }
              }
          }

          使用jps查詢到PrintNumTest的進(jìn)程id,再用下面的命令執(zhí)行MyAttachMain類

          java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/lib/tools.jar:/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.example.aop.agent.MyAttachMain 49987

          可以清楚地看到打印的數(shù)字變成了50

          效果

          Arthas

          以上是我寫的小demo,有很多不足之處,看看大佬是怎么寫的,arthas的trace命令可以統(tǒng)計(jì)方法耗時(shí),如下圖

          Arthas

          搭建調(diào)試環(huán)境

          Arthas debug需要借助IDEA的遠(yuǎn)程debug功能,可以參考 https://github.com/alibaba/arthas/issues/222

          先寫一個(gè)可以循環(huán)執(zhí)行的Demo

          public class ArthasTest {
              public static void main(String[] args) throws InterruptedException {
                  int i = 0;
                  while (true) {
                      Thread.sleep(2000);
                      print(i++);
                  }
              }
              public static void print(Integer content) {
                  System.out.println("Main print: " + content);
              }
          }

          命令行執(zhí)行改demo

          java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000 com.example.aop.agent.ArthasTest

          在Arthas源碼的項(xiàng)目中設(shè)置遠(yuǎn)程debug

          在Arthas源碼的項(xiàng)目中設(shè)置遠(yuǎn)程debug

          在Arthas源碼的項(xiàng)目中設(shè)置遠(yuǎn)程debug

          在這個(gè)方法com.taobao.arthas.agent334.AgentBootstrap#main任意位置打上斷點(diǎn),切換到剛剛設(shè)置的遠(yuǎn)程debug模式,啟動(dòng)項(xiàng)目

          遠(yuǎn)程debug模式

          可以看到剛剛處于Listening的ArthasTest開始執(zhí)行,啟動(dòng)arthas-boot.jar,就可以看到斷點(diǎn)跳進(jìn)Arthas源碼的項(xiàng)目中

          跳進(jìn)Arthas源碼的項(xiàng)目中

          bytekit

          在看trace命令之前需要一點(diǎn)前置知識,使用ASM進(jìn)行字節(jié)碼增強(qiáng),代碼邏輯不好修改,理解困難,所以bytekit基于ASM提供了一套簡潔的API,讓開發(fā)人員可以比較輕松地完成字節(jié)碼增強(qiáng),我們先來看一個(gè)簡單的demo,來自https://github.com/alibaba/bytekit

          public class SampleInterceptor {
              @AtEnter(inline = false, suppress = RuntimeException.class, suppressHandler = PrintExceptionSuppressHandler.class)
              public static void atEnter(@Binding.This Object object,
                                         @Binding.Class Object clazz,
                                         @Binding.Args Object[] args,
                                         @Binding.MethodName String methodName,
                                         @Binding.MethodDesc String methodDesc) {
                  System.out.println("atEnter, args[0]: " + args[0]);
              }

              @AtExit(inline = true)
              public static void atExit(@Binding.Return Object returnObject) {
                  System.out.println("atExit, returnObject: " + returnObject);
              }

              @AtExceptionExit(inline = true, onException = RuntimeException.class)
              public static void atExceptionExit(@Binding.Throwable RuntimeException ex,
                                                 @Binding.Field(name = "exceptionCount") int exceptionCount) {
                  System.out.println("atExceptionExit, ex: " + ex.getMessage() + ", field exceptionCount: " + exceptionCount);
              }
          }
          • 上文說過,bytekit的宗旨是提供簡介的API讓開發(fā)可以輕松地完成字節(jié)碼增強(qiáng),從注解名我們就可以知道@AtEnter是在方法進(jìn)入時(shí)插入,@AtExit是在方法退出時(shí)插入,@AtExceptionExit時(shí)在發(fā)生異常退出時(shí)插入
          • inline = true表示方法中的代碼直接插入增強(qiáng)方法中,inline = false表示是調(diào)用這個(gè)方法,有點(diǎn)難理解,我們等下看反編譯后的代碼
          • 配置了 suppress = RuntimeException.classsuppressHandler = PrintExceptionSuppressHandler.class,說明插入的代碼會被 try/catch 包圍
          • @AtExceptionExit在原方法體范圍try-catch指定異常進(jìn)行處理

          這是我們要進(jìn)行增強(qiáng)的方法

          public class Sample {
              private int exceptionCount = 0;
              public String hello(String str, boolean exception) {
                  if (exception) {
                      exceptionCount++;
                      throw new RuntimeException("test exception, str: " + str);
                  }
                  return "hello " + str;
              }
          }
          public class SampleMain {
              public static void main(String[] args) throws Exception {
                  // 解析定義的 Interceptor類 和相關(guān)的注解
                  DefaultInterceptorClassParser interceptorClassParser = new DefaultInterceptorClassParser();
                  List<InterceptorProcessor> processors = interceptorClassParser.parse(SampleInterceptor.class);
                  // 加載字節(jié)碼
                  ClassNode classNode = AsmUtils.loadClass(Sample.class);
                  // 對加載到的字節(jié)碼做增強(qiáng)處理
                  for (MethodNode methodNode : classNode.methods) {
                      if (methodNode.name.equals("hello")) {
                          MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode);
                          for (InterceptorProcessor interceptor : processors) {
                              interceptor.process(methodProcessor);
                          }
                      }
                  }
                  // 獲取增強(qiáng)后的字節(jié)碼
                  byte[] bytes = AsmUtils.toBytes(classNode);
                  // 查看反編譯結(jié)果
                  System.out.println(Decompiler.decompile(bytes));
                  // 修改Sample
                  AgentUtils.reTransform(Sample.class, bytes);
                  // 執(zhí)行sample的方法
                  try {
                      Sample sample = new Sample();
                      sample.hello("3"false);
                      sample.hello("4"true);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }

          這是Sample反編譯后的結(jié)果,代碼量劇增

          public class Sample {
              private int exceptionCount = 0;

              /*
               * WARNING - void declaration
               */
              public String hello(String string, boolean bl) {
                  try {
                      String string2;
                      void str;
                      void exception;
                      try {
                          // @AtEnter 直接調(diào)用,inline為false的效果
                          SampleInterceptor.atEnter((Object)this, Sample.class, (Object[])new Object[]{string, new Boolean(bl)}, (String)"hello", (String)"(Ljava/lang/String;Z)Ljava/lang/String;");
                      }
                      catch (RuntimeException runtimeException) {
                          Class<Sample> clazz = Sample.class;
                          RuntimeException runtimeException2 = runtimeException;
                          System.out.println("exception handler: " + clazz);
                          runtimeException2.printStackTrace();
                      }
                      if (exception != false) {
                          ++this.exceptionCount;
                          throw new RuntimeException("test exception, str: " + (String)str);
                      }
                      String string3 = string2 = "hello " + (String)str;
                      // @AtExit 代碼直接插入
                      System.out.println("atExit, returnObject: " + string3);
                      return string2;
                  }
                  catch (RuntimeException runtimeException) {
                      int n = this.exceptionCount;
                      RuntimeException runtimeException3 = runtimeException;
                      // @AtExceptionExit 代碼直接插入
                      System.out.println("atExceptionExit, ex: " + runtimeException3.getMessage() + ", field exceptionCount: " + n);
                      throw runtimeException;
                  }
              }
          }

          有了這個(gè)前置知識,我們來看看trace命令

          trace

          trace

          Arthas命令很多,如果是exit、logout、quit、jobs、fg、bg、kill等簡單的命令,就會直接執(zhí)行,如果是trace這種復(fù)雜的命令,會專門用一個(gè)類寫處理的邏輯,如上圖,根據(jù)名字就可以猜到這個(gè)類是處理什么命令的,這么多類的組織形式是模版模式,入口在com.taobao.arthas.core.shell.command.AnnotatedCommand#process,

          public abstract class AnnotatedCommand {
              public abstract void process(CommandProcess process);
          }
          public class TraceCommand extends EnhancerCommand {
          }
          public abstract class EnhancerCommand extends AnnotatedCommand {
              @Override
              public void process(final CommandProcess process) {
                  // ctrl-C support
                  process.interruptHandler(new CommandInterruptHandler(process));
                  // q exit support
                  process.stdinHandler(new QExitHandler(process));
                  // start to enhance
                  enhance(process);
              }
          }

          有一些命令都有字節(jié)碼增強(qiáng)的邏輯,這些邏輯共同封裝在了EnhancerCommand這個(gè)類中,TraceCommand繼承了EnhancerCommand,當(dāng)trace命令執(zhí)行的時(shí)候,增強(qiáng)的邏輯在EnhancerCommand,我們只看核心代碼

          com.taobao.arthas.core.command.monitor200.EnhancerCommand#enhance
          com.taobao.arthas.core.advisor.Enhancer#enhance(java.lang.instrument.Instrumentation)
          public synchronized EnhancerAffect enhance(final Instrumentation inst) throws UnmodifiableClassException {
                  ......
                  try {
                      // 很明顯,這里添加了一個(gè)文件轉(zhuǎn)換器,注意,此處的轉(zhuǎn)換器為本類
                      ArthasBootstrap.getInstance().getTransformerManager().addTransformer(this, isTracing);
                      ......
                  } catch (Throwable e) {
                      logger.error("Enhancer error, matchingClasses: {}", matchingClasses, e);
                      affect.setThrowable(e);
                  }
                  return affect;
              }

          根據(jù)方法名就可以在本類搜索到,具體代碼如下

          @Override
          public byte[] transform(final ClassLoader inClassLoader, String className, Class<?> classBeingRedefined,
                                  ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
              try {
                // 檢查classloader能否加載到 SpyAPI,如果不能,則放棄增強(qiáng)
                try {
                  if (inClassLoader != null) {
                    inClassLoader.loadClass(SpyAPI.class.getName());
                  }
                } catch (Throwable e) {
                  logger.error("the classloader can not load SpyAPI, ignore it. classloader: {}, className: {}",
                               inClassLoader.getClass().getName(), className, e);
                  return null;
                }
                // 這里要再次過濾一次,為啥?因?yàn)樵趖ransform的過程中,有可能還會再誕生新的類
                // 所以需要將之前需要轉(zhuǎn)換的類集合傳遞下來,再次進(jìn)行判斷
                if (matchingClasses != null && !matchingClasses.contains(classBeingRedefined)) {
                  return null;
                }
                // ClassNode中有各種屬性,對應(yīng)Class文件結(jié)構(gòu)
                // keep origin class reader for bytecode optimizations, avoiding JVM metaspace OOM.
                ClassNode classNode = new ClassNode(Opcodes.ASM9);
                ClassReader classReader = AsmUtils.toClassNode(classfileBuffer, classNode);
                // remove JSR https://github.com/alibaba/arthas/issues/1304
                classNode = AsmUtils.removeJSRInstructions(classNode);
                // 重要代碼,生成增強(qiáng)字節(jié)碼的攔截器
                DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
                final List<InterceptorProcessor> interceptorProcessors = new ArrayList<InterceptorProcessor>();
                interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor1.class));
                interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor2.class));
                interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor3.class));
                if (this.isTracing) {
                  // 根據(jù)配置判斷trace命令是否要跳過計(jì)算Java類庫的代碼的耗時(shí)
                  if (!this.skipJDKTrace) {
                    interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor1.class));
                    interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor2.class));
                    interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor3.class));
                  } else {
                    interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor1.class));
                    interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor2.class));
                    interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor3.class));
                  }
                }

                List<MethodNode> matchedMethods = new ArrayList<MethodNode>();
                for (MethodNode methodNode : classNode.methods) {
                  if (!isIgnore(methodNode, methodNameMatcher)) {
                    matchedMethods.add(methodNode);
                  }
                }
                // https://github.com/alibaba/arthas/issues/1690
                if (AsmUtils.isEnhancerByCGLIB(className)) {
                  for (MethodNode methodNode : matchedMethods) {
                    if (AsmUtils.isConstructor(methodNode)) {
                      AsmUtils.fixConstructorExceptionTable(methodNode);
                    }
                  }
                }
                .......
                for (MethodNode methodNode : matchedMethods) {
                  if (AsmUtils.isNative(methodNode)) {
                    logger.info("ignore native method: {}",
                                AsmUtils.methodDeclaration(Type.getObjectType(classNode.name), methodNode));
                    continue;
                  }
                  // 先查找是否有 atBeforeInvoke 函數(shù),如果有,則說明已經(jīng)有trace了,則直接不再嘗試增強(qiáng),直接插入 listener
                  if(AsmUtils.containsMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) {
                    for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
                         .getNext()) {
                      if (insnNode instanceof MethodInsnNode) {
                        final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
                        if(this.skipJDKTrace) {
                          if(methodInsnNode.owner.startsWith("java/")) {
                            continue;
                          }
                        }
                        // 原始類型的box類型相關(guān)的都跳過
                        if(AsmOpUtils.isBoxType(Type.getObjectType(methodInsnNode.owner))) {
                          continue;
                        }
                        AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
                                                                          methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
                      }
                    }
                  }else {
                    // 重點(diǎn)代碼,增強(qiáng)動(dòng)作就是在這里完成的
                    MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
                    for (InterceptorProcessor interceptor : interceptorProcessors) {
                      try {
                        List<Location> locations = interceptor.process(methodProcessor);
                        for (Location location : locations) {
                          if (location instanceof MethodInsnNodeWare) {
                            MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location;
                            MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode();
                            AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
                                                                              methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
                          }
                        }
                      } catch (Throwable e) {
                        logger.error("enhancer error, class: {}, method: {}, interceptor: {}", classNode.name, methodNode.name, interceptor.getClass().getName(), e);
                      }
                    }
                  }

                  // enter/exist 總是要插入 listener
                  AdviceListenerManager.registerAdviceListener(inClassLoader, className, methodNode.name, methodNode.desc,
                                                               listener);
                  affect.addMethodAndCount(inClassLoader, className, methodNode.name, methodNode.desc);
                }

                // https://github.com/alibaba/arthas/issues/1223 , V1_5 的major version是49
                if (AsmUtils.getMajorVersion(classNode.version) < 49) {
                  classNode.version = AsmUtils.setMajorVersion(classNode.version, 49);
                }

                byte[] enhanceClassByteArray = AsmUtils.toBytes(classNode, inClassLoader, classReader);

                // 增強(qiáng)成功,記錄類
                classBytesCache.put(classBeingRedefined, new Object());

                // dump the class
                dumpClassIfNecessary(className, enhanceClassByteArray, affect);

                // 成功計(jì)數(shù)
                affect.cCnt(1);

                return enhanceClassByteArray;
              } catch (Throwable t) {
                logger.warn("transform loader[{}]:class[{}] failed.", inClassLoader, className, t);
                affect.setThrowable(t);
              }
              return null;
          }

          這段代碼很長,其實(shí)主要邏輯就兩個(gè)

          • 解析Interceptor Class的@AtXxx,@Binding等注解,生成InterceptorProcessor對象集合
          • 遍歷InterceptorProcessor集合,修改原方法的字節(jié)碼

          整體的流程如下圖

          整體的流程如圖

          那這些攔截器長什么樣子呢?我們隨便找一個(gè)例子來看看

          public static class SpyInterceptor1 {
              @AtEnter(inline = true)
              public static void atEnter(@Binding.This Object target, @Binding.Class Class<?> clazz,
                                         @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args) {
                SpyAPI.atEnter(clazz, methodInfo, target, args);
              }
          }

          看到這里,就很熟悉了,跟上面bytekit的例子很像,是在方法進(jìn)入時(shí)插入的,當(dāng)然,這里只是淺講一下trace的原理,bytekit背后的原理,需要更底層的知識儲備,我還需要繼續(xù)學(xué)習(xí)。

          ?? 歡迎加入小哈的星球,你將獲得: 專屬的項(xiàng)目實(shí)戰(zhàn) / 1v1 提問 / Java 學(xué)習(xí)路線 / 學(xué)習(xí)打卡 / 每月贈書 / 社群討論

          • 新項(xiàng)目:《從零手?jǐn)]:仿小紅書(微服務(wù)架構(gòu))》 正在持續(xù)爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 點(diǎn)擊查看項(xiàng)目介紹
          • 《從零手?jǐn)]:前后端分離博客項(xiàng)目(全棧開發(fā))》 2期已完結(jié),演示鏈接:http://116.62.199.48/;

          截止目前,累計(jì)輸出 50w+ 字,講解圖 2200+ 張,還在持續(xù)爆肝中.. 后續(xù)還會上新更多項(xiàng)目,目標(biāo)是將 Java 領(lǐng)域典型的項(xiàng)目都整一波,如秒殺系統(tǒng), 在線商城, IM 即時(shí)通訊,Spring Cloud Alibaba 等等,戳我加入學(xué)習(xí),解鎖全部項(xiàng)目,已有1800+小伙伴加入


               
                  

          1. 我的私密學(xué)習(xí)小圈子~

          2. 代碼更新不停機(jī):SpringBoot應(yīng)用實(shí)現(xiàn)零停機(jī)更新的新質(zhì)生產(chǎn)力

          3. 深入剖析 Spring Boot 的 SPI 機(jī)制

          4. 自從用了CheckStyle插件,代碼寫的越來越規(guī)范了....

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          PS:因公眾號平臺更改了推送規(guī)則,如果不想錯(cuò)過內(nèi)容,記得讀完點(diǎn)一下在看,加個(gè)星標(biāo),這樣每次新文章推送才會第一時(shí)間出現(xiàn)在你的訂閱列表里。

          點(diǎn)“在看”支持小哈呀,謝謝啦

          瀏覽 36
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美亚洲日本在线 | 亚洲xxxx护士 | 久久最新网址 | 蜜桃臀久久久蜜桃臀久久久蜜桃臀 | 操鼻素材大全网站免费 |