我想在运行的JVM本身中,从任意方法调用或使用新线程(更容易些)开始动态构建调用图。(该软件将成为用于测试另一使用调用图的软件的负载的测试装置)
我了解有一些SPI接口,但是您似乎需要对它们运行-javaagent标志。我想直接在VM本身中访问它。
理想情况下,我想获取每个方法调用的进入和退出,该方法调用的参数以及该方法中的时间的回调。在单个线程内显然。
我知道AOP可以做到这一点,但是我只是想知道JDK中是否有工具可以让我捕捉到这一点。
JVM没有提供这样的API,即使对于以开头的代理也是如此-javaagent
。JVM
TI是为以该-agent
选项开头的本机代理或调试器提供的本机接口。Java代理可能会使用Instrumentation API,该API提供了类检测的底层功能,但没有直接分析功能。
有两种类型的性能分析实现,即通过采样和通过仪器。
采样通过定期记录堆栈跟踪信息(样本)来进行。这不会跟踪每个方法调用,但仍会检测到热点,因为热点在记录的堆栈跟踪中多次出现。优点是它不需要代理程序或特殊的API,并且您可以控制探查器的开销。您可以通过ThreadMXBean来实现它,该工具允许您获取所有正在运行的线程的堆栈跟踪。实际上,即使a
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();
}
}
}
问题内容: 考虑下面的两个简单的Java类: 第一个例子 第二个例子 程序运行后,如何跟踪(1)哪个对象调用哪个方法(2)以及执行多少次? 稍微精确一点,我可能有100个类和1000个对象,每个对象都调用100多个方法。我希望能够跟踪(在运行程序之后)哪个对象调用了哪种方法以及调用了多少次。 感谢您的任何建议。 问题答案: 这将为所有线程中所有对象的每个方法调用打印一行: 和 您可以使用 hous
我很难找到一种方法来跟踪方法执行(例如:在执行时调用方法...) 下面是我想追踪的代码片段: 我想知道JsonSaniitier类的方法sanitize已被调用... 我尝试运行jStack,但是在堆栈跟踪中没有看到任何JsonSanitizer.sanitize方法的出现。 提前谢谢
java中是否有一种方法可以在不修改代码的情况下打印catch块中任何异常的堆栈跟踪。我被告知有一个JVM arg可以用来生成所有异常的堆栈跟踪以供调试,尽管我在这上面找不到任何留档。我能想到的唯一解决方案是使用aspectj并在创建的任何异常上创建一个方面并打印堆栈跟踪。我希望有比方面更好的解决方案。 谢了Steve --编辑——我想知道的是,假设我有以下代码:试试{throw new Exce
问题内容: 我正在编写一个Java程序,用于从POP3电子邮件中下载附件。最初,我通过获取MimePart的内容类型得到验证的输入流来执行此操作。然后,我可以简单地通过FileOutputStream将输入流写入本地文件。 但是,我遇到的一件事是,当我收到一封带有附件的电子邮件作为唯一内容并访问它之后,该邮件(消息类型)仅由一个部分组成,即文本/普通类型。其内容包括大量随机外观的字符。 通过互联网
问题内容: 有没有一种方法可以为java中的特定线程输出调用跟踪? 我不需要堆栈跟踪。我想在每个对象上进行一系列调用以进行跟踪。 问题答案: 我想您可能会发现这很有趣。它是一个Java代理,它使用slf4j框架将日志记录添加到方法中,从而实际记录输出。然后,只需配置日志记录框架以仅打印出您感兴趣的线程即可。 http://www.slf4j.org/extensions.html#javaagen
在我的主干道上。飞镖 有人能帮我做这个吗? 非常感谢您抽出时间