sirve - ¿Java tiene algún mecanismo para que una máquina virtual rastree las llamadas de método en sí misma, sin usar javaagent, etc.?
que es jvm en programacion (2)
Puede modificar el bytecode de cada método agregando rutina para registrar los eventos de entrada / salida del método. Javassist te ayudará a http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/
También puedes ver un buen tutorial: https://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html
Quiero crear gráficos de llamadas sobre la marcha, comenzando en una llamada de método arbitrario o con un nuevo hilo, lo que siempre es más fácil, desde la propia JVM en ejecución. (esta pieza de software será un elemento de prueba para pruebas de carga otra pieza de software que consume gráficos de llamadas)
Entiendo que hay algunas interfaces SPI, pero parece que necesitas ejecutar -javaagent flag con ellas. Quiero acceder a esto directamente en la propia máquina virtual.
Idealmente, me gustaría obtener una devolución de llamada para la entrada y salida de cada llamada de método, los parámetros de esa llamada de método y el tiempo en ese método. Dentro de un solo hilo obviamente.
Sé que AOP probablemente podría hacer esto, pero me pregunto si hay herramientas dentro del JDK que me permitan capturar esto.
La JVM no proporciona dicha API, ni siquiera para los agentes iniciados con -javaagent
. JVM TI es una interfaz nativa proporcionada para agentes nativos que se inicia con la opción -agent
o para depuradores. Los agentes de Java pueden usar la API de Instrumentation que proporciona la característica de bajo nivel de la instrumentación de clase, pero no la capacidad de creación de perfiles directa.
Hay dos tipos de implementaciones de perfiles, a través de muestreo y a través de instrumentación.
El muestreo funciona registrando las huellas de la pila (muestras) periódicamente. Esto no rastrea todas las llamadas a métodos, pero aún detecta puntos calientes, ya que ocurren varias veces en los seguimientos de pila grabados. La ventaja es que no requiere agentes ni API especiales y usted tiene el control sobre los gastos generales del perfilador. Puede implementarlo a través de ThreadMXBean que le permite obtener trazas de pila de todos los subprocesos en ejecución. De hecho, incluso un Thread.getAllStackTraces()
lo haría, pero ThreadMXBean
proporciona información más detallada sobre los subprocesos.
Por lo tanto, la tarea principal es implementar una estructura de almacenamiento eficiente para los métodos que se encuentran en los seguimientos de la pila, es decir, colapsar las apariciones del mismo método en elementos de un solo árbol de llamadas.
Aquí hay un ejemplo de una muestra muy simple que trabaja en su propia 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);
}
}
}
}
}
Los analizadores que buscan todas las invocaciones de métodos sin pasar por la API de depuración usan la instrumentación para agregar códigos de notificación a todos los métodos que les interesan. La ventaja es que nunca pierden una invocación de métodos, pero por otro lado están agregando una sobrecarga significativa a la ejecución lo que podría influir en el resultado al buscar puntos calientes. Y es mucho más complicado de implementar. No puedo darle un ejemplo de código para tal transformación de código de byte.
La API de Instrumentación se proporciona solo a los agentes de Java, pero en caso de que quiera ir en la dirección de Instrumentación, aquí hay un programa que demuestra cómo conectarse a su propia JVM y cargarse como un agente de 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();
}
}
}