Puede hacerse una idea de la profundidad de la pila con algo parecido a un aspecto que se puede tejer a su código (cargar tejedor de tiempo para permitir el asesoramiento de todo el código cargado excepto el cargador de clase del sistema). El aspecto funcionaría alrededor de todo el código ejecutado y sería capaz de notarlo cuando llame a un método y cuando regrese. Puede usar esto para capturar la mayor parte del uso de la pila (se perderá todo lo cargado desde el cargador de clases del sistema, por ejemplo, java. *). Aunque no es perfecto, evita tener que cambiar tu código para juntar StackTraceElement [] en puntos de muestra y también te lleva al código que no has escrito jdk.
Por ejemplo (aspectj):
public aspect CallStackAdvice {
pointcut allMethods() : execution(* *(..)) && !within(CallStackLog);
Object around(): allMethods(){
String called = thisJoinPoint.getSignature().toLongString();
CallStackLog.calling (called);
try {
return proceed();
} finally {
CallStackLog.exiting (called);
}
}
}
public class CallStackLog {
private CallStackLog() {}
private static ThreadLocal<ArrayDeque<String>> curStack =
new ThreadLocal<ArrayDeque<String>>() {
@Override
protected ArrayDeque<String> initialValue() {
return new ArrayDeque<String>();
}
};
private static ThreadLocal<Boolean> ascending =
new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return true;
}
};
private static ConcurrentHashMap<Integer, ArrayDeque<String>> stacks =
new ConcurrentHashMap<Integer, ArrayDeque<String>>();
public static void calling (String signature) {
ascending.set (true);
curStack.get().push (signature.intern());
}
public static void exiting (String signature) {
ArrayDeque<String> cur = curStack.get();
if (ascending.get()) {
ArrayDeque<String> clon = cur.clone();
stacks.put (hash (clon), clon);
}
cur.pop();
ascending.set (false);
}
public static Integer hash (ArrayDeque<String> a) {
//simplistic and wrong but ok for example
int h = 0;
for (String s : a) {
h += (31 * s.hashCode());
}
return h;
}
public static void dumpStacks(){
//implement something to print or retrieve or use stacks
}
}
y una pila de muestra podría ser como:
net.sourceforge.jtds.jdbc.TdsCore net.sourceforge.jtds.jdbc.JtdsStatement.getTds()
public boolean net.sourceforge.jtds.jdbc.JtdsResultSet.next()
public void net.sourceforge.jtds.jdbc.JtdsResultSet.close()
public java.sql.Connection net.sourceforge.jtds.jdbc.Driver.connect(java.lang.String, java.util.Properties)
public void phil.RandomStackGen.MyRunnable.run()
muy lento y tiene sus propios problemas de memoria, pero puede ser viable para obtener la información de la pila necesitas.
Puede utilizar max_stack y max_locals para cada método en los rastreos de pila para calcular el tamaño de un marco (consulte class file format) para el método. Basado en vm spec, creo que esto debería ser (max_stack + max_locals) * 4bytes para el tamaño de fotograma máximo para un método (long/double ocupa dos entradas en la pila de operando/local vars y se contabiliza en max_stack y max_locals).
Puede javap fácilmente las clases de interés y ver los valores de marco si no tiene mucho en las pilas de llamadas. Y algo como asm le ofrece algunas herramientas fáciles de usar para hacer esto en una escala mayor.
Una vez que haya calculado esto, necesita estimar los marcos de pila adicionales para las clases de JDK que pueden ser llamados por usted en sus puntos de acumulación máxima y agregarlos a sus tamaños de pila. No será perfecto, pero debería darte un punto de partida decente para la afinación de Xss sin hackear el JVM/JDK.
Otra nota: no sé qué hace JIT/OSR para encuadrar los tamaños o los requisitos de pila, así que tenga en cuenta que puede tener diferentes impactos de -Xss en una JVM fría vs. cálida.
EDIT tenían algunas horas de inactividad y lanzaron juntos otro enfoque. Este es un agente de Java que instrumentará métodos para realizar un seguimiento de un tamaño máximo de marco de pila y profundidad de pila. Esto podrá instrumentar la mayoría de las clases de jdk junto con su otro código y bibliotecas, brindándole mejores resultados que el tejedor de aspectos. Necesitas asm v4 para que esto funcione. Era más por el gusto de hacerlo, así que archiva esto bajo la jactanciosa java por diversión, sin ganancias.
primer lugar, hacer algo para rastrear el tamaño y la profundidad del marco de pila:
package phil.agent;
public class MaxStackLog {
private static ThreadLocal<Integer> curStackSize =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
private static ThreadLocal<Integer> curStackDepth =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
private static ThreadLocal<Boolean> ascending =
new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return true;
}
};
private static ConcurrentHashMap<Long, Integer> maxSizes =
new ConcurrentHashMap<Long, Integer>();
private static ConcurrentHashMap<Long, Integer> maxDepth =
new ConcurrentHashMap<Long, Integer>();
private MaxStackLog() { }
public static void enter (int frameSize) {
ascending.set (true);
curStackSize.set (curStackSize.get() + frameSize);
curStackDepth.set (curStackDepth.get() + 1);
}
public static void exit (int frameSize) {
int cur = curStackSize.get();
int curDepth = curStackDepth.get();
if (ascending.get()) {
long id = Thread.currentThread().getId();
Integer max = maxSizes.get (id);
if (max == null || cur > max) {
maxSizes.put (id, cur);
}
max = maxDepth.get (id);
if (max == null || curDepth > max) {
maxDepth.put (id, curDepth);
}
}
ascending.set (false);
curStackSize.set (cur - frameSize);
curStackDepth.set (curDepth - 1);
}
public static void dumpMax() {
int max = 0;
for (int i : maxSizes.values()) {
max = Math.max (i, max);
}
System.out.println ("Max stack frame size accummulated: " + max);
max = 0;
for (int i : maxDepth.values()) {
max = Math.max (i, max);
}
System.out.println ("Max stack depth: " + max);
}
}
A continuación, hacer que el agente de java:
package phil.agent;
public class Agent {
public static void premain (String agentArguments, Instrumentation ins) {
try {
ins.appendToBootstrapClassLoaderSearch (
new JarFile (
new File ("path/to/Agent.jar")));
} catch (IOException e) {
e.printStackTrace();
}
ins.addTransformer (new Transformer(), true);
Class<?>[] classes = ins.getAllLoadedClasses();
int len = classes.length;
for (int i = 0; i < len; i++) {
Class<?> clazz = classes[i];
String name = clazz != null ? clazz.getCanonicalName() : null;
try {
if (name != null && !clazz.isArray() && !clazz.isPrimitive()
&& !clazz.isInterface()
&& !name.equals ("java.lang.Long")
&& !name.equals ("java.lang.Boolean")
&& !name.equals ("java.lang.Integer")
&& !name.equals ("java.lang.Double")
&& !name.equals ("java.lang.Float")
&& !name.equals ("java.lang.Number")
&& !name.equals ("java.lang.Class")
&& !name.equals ("java.lang.Byte")
&& !name.equals ("java.lang.Void")
&& !name.equals ("java.lang.Short")
&& !name.equals ("java.lang.System")
&& !name.equals ("java.lang.Runtime")
&& !name.equals ("java.lang.Compiler")
&& !name.equals ("java.lang.StackTraceElement")
&& !name.startsWith ("java.lang.ThreadLocal")
&& !name.startsWith ("sun.")
&& !name.startsWith ("java.security.")
&& !name.startsWith ("java.lang.ref.")
&& !name.startsWith ("java.lang.ClassLoader")
&& !name.startsWith ("java.util.concurrent.atomic")
&& !name.startsWith ("java.util.concurrent.ConcurrentHashMap")
&& !name.startsWith ("java.util.concurrent.locks.")
&& !name.startsWith ("phil.agent.")) {
ins.retransformClasses (clazz);
}
} catch (Throwable e) {
System.err.println ("Cant modify: " + name);
}
}
Runtime.getRuntime().addShutdownHook (new Thread() {
@Override
public void run() {
MaxStackLog.dumpMax();
}
});
}
}
La clase agente tiene el gancho premain para la instrumentación. En ese gancho, agrega un transformador de clase que los instrumentos en el seguimiento del tamaño del marco de pila. También agrega el agente al cargador de clases de arranque para que también pueda procesar clases jdk. Para hacer eso, necesitamos retransformar todo lo que ya se haya cargado, como String.class.Pero tenemos que excluir una variedad de cosas que el agente o el registro de la pila utilizan, lo que conduce a ciclos infinitos u otros problemas (algunos de los cuales se encontraron por prueba y error). Finalmente, el agente agrega un gancho de cierre para volcar los resultados a stdout.
public class Transformer implements ClassFileTransformer {
@Override
public byte[] transform (ClassLoader loader,
String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
if (className.startsWith ("phil/agent")) {
return classfileBuffer;
}
byte[] result = classfileBuffer;
ClassReader reader = new ClassReader (classfileBuffer);
MaxStackClassVisitor maxCv = new MaxStackClassVisitor (null);
reader.accept (maxCv, ClassReader.SKIP_DEBUG);
ClassWriter writer = new ClassWriter (ClassWriter.COMPUTE_FRAMES);
ClassVisitor visitor =
new CallStackClassVisitor (writer, maxCv.frameMap, className);
reader.accept (visitor, ClassReader.SKIP_DEBUG);
result = writer.toByteArray();
return result;
}
}
El transformador acciona dos transformaciones separadas - uno para averiguar el tamaño de marco de pila max para cada método y uno para instrumentar el método para la grabación. Podría ser factible en una sola pasada, pero no quería usar la API de árbol de ASM ni pasar más tiempo averiguándolo.
public class MaxStackClassVisitor extends ClassVisitor {
Map<String, Integer> frameMap = new HashMap<String, Integer>();
public MaxStackClassVisitor (ClassVisitor v) {
super (Opcodes.ASM4, v);
}
@Override
public MethodVisitor visitMethod (int access, String name,
String desc, String signature,
String[] exceptions) {
return new MaxStackMethodVisitor (
super.visitMethod (access, name, desc, signature, exceptions),
this, (access + name + desc + signature));
}
}
public class MaxStackMethodVisitor extends MethodVisitor {
final MaxStackClassVisitor cv;
final String name;
public MaxStackMethodVisitor (MethodVisitor mv,
MaxStackClassVisitor cv, String name) {
super (Opcodes.ASM4, mv);
this.cv = cv;
this.name = name;
}
@Override
public void visitMaxs (int maxStack, int maxLocals) {
cv.frameMap.put (name, (maxStack + maxLocals) * 4);
super.visitMaxs (maxStack, maxLocals);
}
}
Las clases MaxStack * visitantes manejan averiguar el tamaño del marco de pila máximo.
public class CallStackClassVisitor extends ClassVisitor {
final Map<String, Integer> frameSizes;
final String className;
public CallStackClassVisitor (ClassVisitor v,
Map<String, Integer> frameSizes, String className) {
super (Opcodes.ASM4, v);
this.frameSizes = frameSizes;
this.className = className;
}
@Override
public MethodVisitor visitMethod (int access, String name,
String desc, String signature, String[] exceptions) {
MethodVisitor m = super.visitMethod (access, name, desc,
signature, exceptions);
return new CallStackMethodVisitor (m,
frameSizes.get (access + name + desc + signature));
}
}
public class CallStackMethodVisitor extends MethodVisitor {
final int size;
public CallStackMethodVisitor (MethodVisitor mv, int size) {
super (Opcodes.ASM4, mv);
this.size = size;
}
@Override
public void visitCode() {
visitIntInsn (Opcodes.SIPUSH, size);
visitMethodInsn (Opcodes.INVOKESTATIC, "phil/agent/MaxStackLog",
"enter", "(I)V");
super.visitCode();
}
@Override
public void visitInsn (int inst) {
switch (inst) {
case Opcodes.ARETURN:
case Opcodes.DRETURN:
case Opcodes.FRETURN:
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.RETURN:
case Opcodes.ATHROW:
visitIntInsn (Opcodes.SIPUSH, size);
visitMethodInsn (Opcodes.INVOKESTATIC,
"phil/agent/MaxStackLog", "exit", "(I)V");
break;
default:
break;
}
super.visitInsn (inst);
}
}
Las clases de visitante de CallStack * manejan métodos de instrumentación con código para llamar al registro de la pila de marco.
Y entonces usted necesita un MANIFEST.MF para el Agent.jar:
Manifest-Version: 1.0
Premain-Class: phil.agent.Agent
Boot-Class-Path: asm-all-4.0.jar
Can-Retransform-Classes: true
Por último, añadir lo siguiente a la línea de comandos java para el programa que desea instrumento:
-javaagent:path/to/Agent.jar
También necesitará tener asm-all-4.0.jar en el mismo directorio que Agent.jar (o cambiar Boot-Class-Path en el manifiesto para hacer referencia a la ubicación).
Un ejemplo del resultado podría ser:
Max stack frame size accummulated: 44140
Max stack depth: 1004
Todo esto es un poco crudo, pero funciona para mí para ponerse en marcha.
Nota: el tamaño del marco de pila no es un tamaño de pila total (todavía no se sabe cómo obtenerlo). En la práctica, hay una variedad de gastos generales para la pila de subprocesos. Descubrí que, por lo general, necesitaba entre 2 y 3 veces el tamaño de fotograma máximo de la pila como valor -Xss. Ah, y asegúrese de hacer la afinación -Xss sin cargar el agente, ya que se suma a los requisitos de tamaño de la pila.
Debería considerar cambiar al modelo asíncrono. No tiene sentido tener más hilos que núcleos de CPU en el sistema. –
@VladLazarenko - de acuerdo. Como dije, a largo plazo planeo reducir la proporción de subprocesos por usuario, pero necesito una solución rápida antes. –
¿Cuántos hilos estás creando y cómo los estás administrando? ¿Y por qué necesita un hilo por usuario, no puede reutilizar los hilos y ofrecer hilo por solicitud? –