java是否有任何机制可以让VM在不使用javaagent等的情况下跟踪自身的方法调用?

2023-12-07

我想动态构建调用图,从任意方法调用开始,或者从运行的 JVM 本身内部的新线程开始,哪一个更容易。 (这个软件将成为一个测试装置,用于对另一个使用调用图的软件进行负载测试)

我知道有一些 SPI 接口,但看起来您需要使用它们运行 -javaagent 标志。我想直接在虚拟机本身中访问它。

理想情况下,我希望获得每个方法调用的进入和退出、该方法调用的参数以及该方法中的时间的回调。显然是在单个线程内。

我知道 AOP 可能可以做到这一点,但我只是想知道 JDK 中是否有工具可以让我捕获这一点。


JVM 没有提供这样的 API——即使对于以-javaagent。 JVM TI 是为以 JVM 启动的本机代理提供的本机接口-agent选项或用于调试器。 Java 代理可能会使用仪器仪表API 提供类检测的低级功能,但没有直接分析功能。

有两种类型的分析实现,通过采样和通过检测。

采样通过定期记录堆栈跟踪(样本)来进行。这不会跟踪每个方法调用,但仍会检测热点,因为它们在记录的堆栈跟踪中多次出现。优点是它不需要代理或特殊的 API,并且您可以控制探查器的开销。您可以通过以下方式实现它线程MXBean它允许您获取所有正在运行的线程的堆栈跟踪。事实上,即使是一个Thread.getAllStackTraces()会做但是ThreadMXBean提供有关线程的更多详细信息。

因此,主要任务是为堆栈跟踪中找到的方法实现有效的存储结构,即将出现的相同方法折叠为单个调用树项。

下面是一个非常简单的采样器在其自己的 JVM 上工作的示例:

import java.lang.Thread.State;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Sampler {
  private static final ThreadMXBean TMX=ManagementFactory.getThreadMXBean();
  private static String CLASS, METHOD;
  private static CallTree ROOT;
  private static ScheduledExecutorService EXECUTOR;

  public static synchronized void startSampling(String className, String method) {
    if(EXECUTOR!=null) throw new IllegalStateException("sampling in progress");
    System.out.println("sampling started");
    CLASS=className;
    METHOD=method;
    EXECUTOR = Executors.newScheduledThreadPool(1);
    // "fixed delay" reduces overhead, "fixed rate" raises precision
    EXECUTOR.scheduleWithFixedDelay(new Runnable() {
      public void run() {
        newSample();
      }
    }, 150, 75, TimeUnit.MILLISECONDS);
  }
  public static synchronized CallTree stopSampling() throws InterruptedException {
    if(EXECUTOR==null) throw new IllegalStateException("no sampling in progress");
    EXECUTOR.shutdown();
    EXECUTOR.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
    EXECUTOR=null;
    final CallTree root = ROOT;
    ROOT=null;
    return root;
  }
  public static void printCallTree(CallTree t) {
    if(t==null) System.out.println("method not seen");
    else printCallTree(t, 0, 100);
  }
  private static void printCallTree(CallTree t, int ind, long percent) {
    long num=0;
    for(CallTree ch:t.values()) num+=ch.count;
    if(num==0) return;
    for(Map.Entry<List<String>,CallTree> ch:t.entrySet()) {
      CallTree cht=ch.getValue();
      StringBuilder sb = new StringBuilder();
      for(int p=0; p<ind; p++) sb.append(' ');
      final long chPercent = cht.count*percent/num;
      sb.append(chPercent).append("% (").append(cht.cpu*percent/num)
        .append("% cpu) ").append(ch.getKey()).append(" ");
      System.out.println(sb.toString());
      printCallTree(cht, ind+2, chPercent);
    }
  }
  static class CallTree extends HashMap<List<String>, CallTree> {
    long count=1, cpu;
    CallTree(boolean cpu) { if(cpu) this.cpu++; }
    CallTree getOrAdd(String cl, String m, boolean cpu) {
      List<String> key=Arrays.asList(cl, m);
      CallTree t=get(key);
      if(t!=null) { t.count++; if(cpu) t.cpu++; }
      else put(key, t=new CallTree(cpu));
      return t;
    }
  }
  static void newSample() {
    for(ThreadInfo ti:TMX.dumpAllThreads(false, false)) {
      final boolean cpu = ti.getThreadState()==State.RUNNABLE;
      StackTraceElement[] stack=ti.getStackTrace();
      for(int ix = stack.length-1; ix>=0; ix--) {
        StackTraceElement ste = stack[ix];
        if(!ste.getClassName().equals(CLASS)||!ste.getMethodName().equals(METHOD))
          continue;
        CallTree t=ROOT;
        if(t==null) ROOT=t=new CallTree(cpu);
        for(ix--; ix>=0; ix--) {
          ste = stack[ix];
          t=t.getOrAdd(ste.getClassName(), ste.getMethodName(), cpu);
        }
      }
    }
  }
}

探查器在不通过调试 API 的情况下寻找每个方法调用,使用检测将通知代码添加到他们感兴趣的每个方法中。优点是他们永远不会错过方法调用,但另一方面,他们给执行增加了显着的开销这可能会影响搜索热点时的结果。而且实施起来要复杂得多。我无法为您提供此类字节码转换的代码示例。

Instrumentation API 仅提供给 Java 代理,但如果您想进入 Instrumentation 方向,这里有一个程序演示如何连接到其自己的 JVM 并将其自身加载为 Java 代理:

import java.io.*;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

// this API comes from the tools.jar of your JDK
import com.sun.tools.attach.*;

public class SelfAttacher {

  public static Instrumentation BACK_LINK;

  public static void main(String[] args) throws Exception {
 // create a special property to verify our JVM connection
    String magic=UUID.randomUUID().toString()+'/'+System.nanoTime();
    System.setProperty("magic", magic);
 // the easiest way uses the non-standardized runtime name string
    String name=ManagementFactory.getRuntimeMXBean().getName();
    int ix=name.indexOf('@');
    if(ix>=0) name=name.substring(0, ix);
    VirtualMachine vm;
    getVM: {
      try {
      vm = VirtualMachine.attach(name);
      if(magic.equals(vm.getSystemProperties().getProperty("magic")))
        break getVM;
      } catch(Exception ex){}
 //   if the easy way failed, try iterating over all local JVMs
      for(VirtualMachineDescriptor vd:VirtualMachine.list()) try {
        vm=VirtualMachine.attach(vd);
        if(magic.equals(vm.getSystemProperties().getProperty("magic")))
          break getVM;
        vm.detach();
      } catch(Exception ex){}
 //   could not find our own JVM or could not attach to it
      return;
    }
    System.out.println("attached to: "+vm.id()+'/'+vm.provider().type());
    vm.loadAgent(createJar().getAbsolutePath());
    synchronized(SelfAttacher.class) {
      while(BACK_LINK==null) SelfAttacher.class.wait();
    }
    System.out.println("Now I have hands on instrumentation: "+BACK_LINK);
    System.out.println(BACK_LINK.isModifiableClass(SelfAttacher.class));
    vm.detach();
  }
 // create a JAR file for the agent; since our class is already in class path
 // our jar consisting of a MANIFEST declaring our class as agent only
  private static File createJar() throws IOException {
    File f=File.createTempFile("agent", ".jar");
    f.deleteOnExit();
    Charset cs=StandardCharsets.ISO_8859_1;
    try(FileOutputStream fos=new FileOutputStream(f);
        ZipOutputStream os=new ZipOutputStream(fos)) {
      os.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
      ByteBuffer bb = cs.encode("Agent-Class: "+SelfAttacher.class.getName());
      os.write(bb.array(), bb.arrayOffset()+bb.position(), bb.remaining());
      os.write(10);
      os.closeEntry();
    }
    return f;
  }
 // invoked when the agent is loaded into the JVM, pass inst back to the caller
  public static void agentmain(String agentArgs, Instrumentation inst) {
    synchronized(SelfAttacher.class) {
      BACK_LINK=inst;
      SelfAttacher.class.notifyAll();
    }
  }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java是否有任何机制可以让VM在不使用javaagent等的情况下跟踪自身的方法调用? 的相关文章

  • 可序列化对象的 ArrayList 的加密保存和解密加载

    我在 SD 卡中保存并加载一个文件 其中包含ArrayList使用这两种方法的可序列化对象 保存方法 public static void saveUserList ArrayList
  • 有效地将三个字母的货币名称转换为符号名称(例如 20 美元到 20 美元)

    我有一个格式化的字符串 它等于USD 20 我想把它转换成 20 我怎样才能高效地做到这一点 我应该使用正则表达式来执行此操作 但由于区域设置发生变化 国家 地区 ISOCode 也会发生变化 你需要的是这个 import java uti
  • 仅运行相应源代码已更改的单元测试?

    我正在 Jenkins CI 服务器中运行单元测试和 Selenium 测试 众所周知 在大型项目中测试需要很长时间才能运行 Java 是否有一个工具 框架只能触发其源代码已更改的测试 这是因为并非每次对 SCM 的提交都会影响源代码的所有
  • 在 Spark 中写入 JSON 时保留具有空值的键

    我正在尝试使用 Spark 编写 JSON 文件 有一些键有null作为价值 这些在中显示得很好DataSet 但是当我写入文件时 密钥会丢失 我如何确保它们被保留 写入文件的代码 ddp coalesce 20 write mode ov
  • 序列化 ArrayList

    我正在尝试编写一个 Android 游戏 即使用户想要返回主菜单或者活动被系统终止 我也希望能够暂停游戏 onSaveInstanceState 似乎并没有给我很大的控制权来决定何时可以读回捆绑包 而且据我所知 捆绑包仅在短时间内有效 所以
  • 合并 2 个 .jks 信任库文件

    我正在使用启用了 SSL 的 Tomcat 并使用信任库进行客户端身份验证 我有两个 jks trustore 文件 第一个 我将其用于 PROD 环境 另一个用于 TEST 环境客户端证书 我在 Tomcat 上部署了 Web 应用程序
  • 如何将参数传递给Workmanager DoWork方法

    我想安排任务在 24 小时后从数据库中删除 public class WorkManager extends Worker public WorkManager NonNull Context context NonNull WorkerP
  • 如何从球衣服务端点发送实体列表?

    我正在从球衣服务器发送实体列表 在客户端 我试图获取这些实体列表 但它给了元帅例外 为什么它在元素名末尾添加 s 即 emps 而不是 emp XmlRootElement public class Emp Server side code
  • 如何在 Android 中签署 AAR Artifacts?

    我目前正在开发一个 AAR android 库 我想用我自己的密钥对已发布的工件进行签名 以便我可以确定我是否发布了具有相同名称和功能的假 aar 注意事项1 我希望能够以编程方式检查我的库的真实性 即使是一个伪造的库 只是伪造了我的 aa
  • Byte[] 和 java.lang.OutOfMemoryError 按位读/写文件

    我正在努力擦除 Android 中的一些可用空间 这是我的代码 private void creatingFileDelete int size int passMode File lastFile new File Environment
  • 简单的Java程序插入USB热点后速度慢100倍

    我有以下Java程序 class Main public static void main String args throws java io IOException long start System nanoTime java io
  • java多线程中“私有最终对象”锁定有什么用?

    java多线程中 私有最终对象 锁定有什么用 据我的理解 我认为要使一个类成为线程安全的 我们应该使用内部锁定 将所有方法标记为同步并使用 this 将它们锁定在对象的监视器上 或者我们可以用方法中的私有最终对象锁替换类的 this 上标记
  • 是否有适用于 Java 的 CalDAV 客户端库? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我想使用 CalDAV 协议与我的日
  • java内存不足然后退出

    我有一个必须分析大文件的软件 限制输入或提供无限内存都不是一个选择 所以我必须忍受飞行的 OOME 因为 OOME 只杀死线程 所以我的软件运行在一些糟糕的状态 从外面看一切都很好 因为进程正在运行 但在内部却是脑死亡 我想拔掉它的插头 但
  • SFTP Java - 管道关闭 Jsch 异常

    我正在研究一种 java 方法 将文件从一个位置复制到另一个远程位置 我的代码如下 我尝试使用jsch 0 1 42 0 1 50 0 1 54 public static void processFiles ArrayList
  • 将序列化数据发送到 servlet 时出现 java.io.EOFException

    我正在尝试从 Java 本地应用程序上传一个包含文件到服务器的对象 我的计划是 在 tomcat 上运行的 servlet 将使用以下方法获取对象ObjectInputStream in the doGet方法 但我得到一个EOFExcep
  • Java 中有类似 .NET 的 NotImplementedException 的东西吗?

    有没有类似 NET 的东西NotImplementedException在Java中 康芒斯朗 http commons apache org proper commons lang javadocs api 2 6 org apache
  • 原子整数的compareandexchange()与compareandset()

    在研究 AtomicInteger 时 我发现这个 API 提供了两种方法 比较和交换 如果当前值被引用 则自动将该值设置为 newValue to 作为见证值 预期值 记忆效应为 由指定VarHandle compareAndExchan
  • 与手动搜索列表相比,Collections.binarySearch 的性能如何?

    我想知道该使用哪一个 我有一份学生名单 我想用他的名字搜索一个学生 到目前为止 我是通过迭代列表手动完成的 如下所示 for int i 0 i lt list size i Student student list get i if st
  • 如何将元素添加到通用集合

    我想知道如何将专用对象添加到通用集合中 我正在使用以下代码 Collection

随机推荐