java是否有任何机制让VM自己跟踪方法调用,而不使用javaagent等? [英] Does java have any mechanism for a VM to trace method calls on itself, without using javaagent, etc?

查看:150
本文介绍了java是否有任何机制让VM自己跟踪方法调用,而不使用javaagent等?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望动态构建调用图,从任意方法调用开始,或者从运行的JVM本身更容易的新线程开始。 (这个软件将成为负载测试另一个消耗调用图的软件的测试工具)



我知道有一些SPI接口,但它看起来很像就像你需要用它们运行-javaagent标志一样。我想直接在VM本身访问它。



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



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

解决方案

JVM没有提供这样的API - 即使是以 -javaagent开头的代理。 JVM TI是为使用 -agent 选项或调试器启动的本机代理提供的本机接口。 Java代理可能使用 Instrumentation API,它提供了类检测的低级特性,但没有直接的分析功能。



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



采样通过定期记录堆栈跟踪(样本)来工作。这不会跟踪每个方法调用,但仍会检测到在记录的堆栈跟踪中多次出现的热点。优点是它不需要代理也不需要特殊的API,您可以控制分析器的开销。您可以通过 ThreadMXBean 实现它,它可以让您获得堆栈所有正在运行的线程的跟踪事实上,即使 Thread.getAllStackTraces()也可以,但 ThreadMXBean 提供了有关线程的更多详细信息。 / p>

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



下面是一个非常简单的采样器在自己的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 Sc​​heduledExecutorService EXECUTOR;

public static synchronized void startSampling(String className,String method){
if(EXECUTOR!= null)抛出新的IllegalStateException(正在进行采样);
System.out.println(sampling started);
CLASS = className;
METHOD =方法;
EXECUTOR = Executors.newScheduledThreadPool(1);
//固定延迟减少了开销,固定费率提高了精度
EXECUTOR.scheduleWithFixedDelay(new Runnable(){
public void run(){
newSample();
}
},150,75,TimeUnit.MILLISECONDS);
}
公共静态同步CallTree stopSampling()抛出InterruptedException {
if(EXECUTOR == null)抛出新的IllegalStateException(没有正在进行的采样);
EXECUTOR.shutdown();
EXECUTOR.awaitTermination(Long.MAX_VALUE,TimeUnit.DAYS);
EXECUTOR = null;
final CallTree root = ROOT;
ROOT = null;
返回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);
}
}
静态类CallTree扩展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));
返回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的情况下寻找每个方法调用的Profilers使用检测来为他们感兴趣的每个方法添加通知代码。优点是他们永远不会错过任何方法但是另一方面,它们会给执行带来很大的开销,这可能会影响搜索热点时的结果。而且实施起来更复杂。我不能给你一个这样的字节码转换的代码示例。



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;

//此API来自JDK
import com.sun.tools.attach。*的tools.jar。

公共类SelfAttacher {

public static Instrumentation BACK_LINK;

public static void main(String [] args)throws Exception {
//创建一个特殊属性来验证我们的JVM连接
String magic = UUID.randomUUID()。toString ()+ '/' + System.nanoTime();
System.setProperty(魔法,魔术);
//最简单的方法是使用非标准化的运行时名称字符串
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){}
//如果简单方法失败,请尝试迭代所有本地JVM
for(VirtualMachineDescriptor vd:VirtualMachine.list())try {
vm = VirtualMachine.attach(vd);
if(magic.equals(vm.getSystemProperties()。getProperty(magic)))
break getVM;
vm.detach();
} catch(Exception ex){}
//找不到我们自己的JVM或无法附加到它
return;
}
System.out.println(附加到:+ vm.id()+'/'+ vm.provider()。type());
vm.loadAgent(createJar()。getAbsolutePath());
synchronized(SelfAttacher.class){
while(BACK_LINK == null)SelfAttacher.class.wait();
}
System.out.println(现在我手上有仪器:+ BACK_LINK);
System.out.println(BACK_LINK.isModifiableClass(SelfAttacher.class));
vm.detach();
}
//为代理创建一个JAR文件;因为我们的类已经在类路径
//我们的jar由一个MANIFEST组成,声明我们的类只作为代理
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();
}
返回f;
}
//在代理加载到JVM时调用,将inst传递给调用者
public static void agentmain(String agentArgs,Instrumentation inst){
synchronized(SelfAttacher) .class){
BACK_LINK = inst;
SelfAttacher.class.notifyAll();
}
}
}


I want to build call graphs on the fly, starting at an arbitrary method call or with a new thread, which ever is easier, from within the running JVM itself. (this piece of software is going to be a test fixture for load testing another piece of software that consumes call graphs)

I understand there are some SPI interfaces, but it looks like you need to run -javaagent flag with them. I want to access this directly in the VM itself.

Ideally, I'd like to get a callback for entry and exit of each method call, parameters to that method call, and time in that method. Within a single thread obviously.

I know AOP could probably do this, but I'm just wondering if there are tools within the JDK that would allow me to capture this.

解决方案

There is no such API provided by the JVM— even for agents started with -javaagent. The JVM TI is a native interface provided for native agents started with the -agent option or for debuggers. Java agents might use the Instrumentation API which provides the lowlevel feature of class instrumentation but no direct profiling capability.

There are two types of profiling implementations, via sampling and via instrumentation.

Sampling works by recording stack traces (samples) periodically. This does not trace every method call but still detect hot spots as they occur multiple times in the recorded stack traces. The advantage is that it does not require agents nor special APIs and you have the control over the profiler’s overhead. You can implement it via the ThreadMXBean which allows you to get stack traces of all running threads. In fact, even a Thread.getAllStackTraces() would do but the ThreadMXBean provides more detailed information about the threads.

So the main task is to implement an efficient storage structure for the methods found in the stack traces, i.e. collapsing occurrences of the same method into single call tree items.

Here is an example of a very simple sampler working on its own 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);
        }
      }
    }
  }
}


Profilers hunting for every method invocation without going through the debugging API use instrumentation to add notification code to every method they are interested in. The advantage is that they never miss a method invocation but on the other hand they are adding a significant overhead to the execution which might influence the result when searching for hot spots. And it’s way more complicated to implement. I can’t give you a code example for such a byte code transformation.

The Instrumentation API is provided to Java agents only but in case you want to go into the Instrumentation direction, here is a program which demonstrates how to connect to its own JVM and load itself as a Java agent:

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();
    }
  }
}

这篇关于java是否有任何机制让VM自己跟踪方法调用,而不使用javaagent等?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆