2013-11-08 2 views
19

Я хочу построить диаграммы вызовов «на лету», начиная с произвольного вызова метода или с помощью нового потока, который когда-либо проще, из самого запущенного JVM. (эта часть программного обеспечения будет тестовым прибором для тестирования нагрузки другой части программного обеспечения, которая потребляет графики вызовов)Имеет ли java какой-либо механизм для виртуальной машины для отслеживания вызовов методов без использования javaagent и т. Д.?

Я понимаю, что есть некоторые интерфейсы SPI, но похоже, что с ними нужно запустить флаг javaagent. Я хочу получить доступ к нему непосредственно в самой виртуальной машине.

В идеале, я хотел бы получить обратный вызов для ввода и выхода каждого вызова метода, параметров этого вызова метода и времени в этом методе. Очевидно, в пределах одного потока.

Я знаю, что AOP, вероятно, может это сделать, но мне просто интересно, есть ли в JDK инструменты, которые позволили бы мне захватить это.

+1

Вы могли бы, возможно, взглянуть на то, что текущий интерфейс Java отладчик, так как VisualVM должен быть в состоянии работать * как-то *. (Кажется, это [JPDA] (http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/) и материал, который он охватывает.) Тем не менее, я сомневаюсь, что вы получите очень конкретное руководство что касается конкретной задачи, которую вы хотите сделать. (Я также не могу гарантировать, что интерфейс отладчика может даже выполнить его.) – millimoose

+0

@millimoose - Мне показалось, что я видел интерфейс SPI для получения этой информации где-то в JDK, но я не могу ее найти сейчас. Возможно, я ошибся. – marathon

ответ

20

Нет такого API, предоставляемого JVM- даже для агентов, начинающихся с -javaagent. JVM TI - это собственный интерфейс, предоставляемый для собственных агентов, запущенных с помощью опции -agent или для отладчиков. Java-агенты могут использовать API-интерфейс Instrumentation, который обеспечивает низкоуровневую функцию инструментария класса, но не имеет возможности прямого профилирования.

Существует два типа профилирующих реализаций, с помощью выборки и с помощью инструментария.

Сэмплирование работает путем записи следов стека (образцов) периодически. Это не отслеживает все вызовы метода, но все же обнаруживает горячие точки, поскольку они происходят несколько раз в записанных трассировках стека. Преимущество заключается в том, что он не требует наличия агентов или специальных API-интерфейсов, и у вас есть контроль над служебными данными профайлера. Вы можете реализовать его с помощью ThreadMXBean, который позволяет получать трассировки стека всех запущенных потоков. Фактически, даже Thread.getAllStackTraces() будет делать, но ThreadMXBean предоставляет более подробную информацию о потоках.

Таким образом, основная задача состоит в том, чтобы реализовать эффективную структуру хранения для методов, найденных в трассировке стека, т. Е. Сворачивание вхождения одного и того же метода в элементы дерева вызовов.

Вот пример очень простой сэмплер работает на своей виртуальной машины Java:

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 инструментария для добавления кода уведомления для каждого метода они заинтересованы. Преимущество заключается в том, что они никогда не пропускают вызов метода, но, с другой стороны, они добавляют значительные накладные расходы на выполнение, которые могут повлиять на результат при поиске горячих точек. И это сложнее реализовать. Я не могу дать вам пример кода для такого преобразования байтового кода.

инструментария API предоставляется только Java агентов, но в случае, если вы хотите идти в направлении Instrumentation, здесь это программа, которая показывает, как подключиться к своей собственной виртуальной машины Java и загрузить себя в качестве 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(); 
    } 
    } 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^