如何测量我的应用程序的平均和最大堆栈大小,以指导我的决策获得最佳的-Xss值?我对两种可能的方法感兴趣
>在集成测试期间测量正在运行的JVM.哪些分析工具会报告最大堆栈深度?
>静态分析应用程序,寻找深层次的层次结构.依赖注入的反思使得这不太可能会起作用.
更新:我知道解决这个问题的长期正确方法.请关注我问的问题:如何测量堆栈深度?
更新2:有一个关于JProfiler的相关问题,我得到了一个很好的答案:Can JProfiler measure stack depth?(根据JProfiler的社区支持建议,我发布了单独的问题)
解决方法
例如(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 } }
一个示例堆栈可能如下所示:
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()
很慢,有自己的内存问题,但可以让您获得所需的堆栈信息.
然后,您可以使用堆栈跟踪中的每个方法的max_stack和max_locals来计算该方法的框架大小(参见class file format).基于vm spec我相信这应该是(max_stack max_locals)* 4bytes用于方法的最大帧大小(long / double占用操作数堆栈/本地vars上的两个条目,并在max_stack和max_locals中占用).
您可以轻松地对感兴趣的类进行javap,如果在调用堆栈中没有那么多,可以查看帧值.像asm这样的东西为您提供了一些更简单的工具,可以在更大的范围内使用这些工具.
一旦计算出来,您需要估计JDK类的额外堆栈帧,您可以在最大堆栈点调用它们,并将其添加到堆栈大小.它不会是完美的,但是它应该让您成为-Xss调优的一个不错的起点,而不会在JVM / JDK周围进行攻击.
另一个注意事项:我不知道JIT / OSR对框架大小或堆栈要求做了什么,因此请注意,可能会对-Xvs调整对冷或暖JVM有不同的影响.
EDIT有几个小时的停机时间,并投掷了另一种方法.这是一个java代理,它将仪器方法跟踪最大堆栈帧大小和堆栈深度.这将能够将大多数jdk类与其他代码和库一起使用,为您提供比方面编织者更好的结果.你需要asm v4才能工作.它更多的是为它的乐趣,所以将这个在plinky java下的乐趣,而不是利润.
首先,做一些跟踪堆栈帧大小和深度的东西:
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 ); } }
接下来,让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 (); } } ); } }
代理类具有用于仪表的premain钩.在那个钩子中,它添加了一个类型的变压器,它在堆栈框架大小跟踪中进行工具.它还将代理添加到引导类加载器,以便它也可以处理jdk类.为了做到这一点,我们需要重新传输任何可能已经加载的东西,比如String.class.但是,我们必须排除代理使用的各种事物或导致无限循环或其他问题的堆栈日志记录(其中一些是通过反复和错误发现的).最后,代理添加了一个关闭钩子来将结果转储到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; } }
变压器驱动两个单独的变换 – 一个用于计算每个方法的最大堆栈帧大小,一个用于记录方法.它可能在一次通过可行,但我不想使用ASM树API或花更多的时间来弄清楚.
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 ); } }
MaxStack * Visitor类处理最大堆栈帧大小.
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[] exceptions ) { MethodVisitor m = super.visitMethod ( access,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,"exit","(I)V" ); break; default: break; } super.visitInsn ( inst ); } }
CallStack * Visitor类处理具有调用堆栈帧记录的代码的检测方法.
然后你需要一个MANIFEST.MF的Agent.jar:
Manifest-Version: 1.0 Premain-Class: phil.agent.Agent Boot-Class-Path: asm-all-4.0.jar Can-Retransform-Classes: true
-javaagent:path/to/Agent.jar
您还需要将asm-all-4.0.jar与Agent.jar相同的目录(或更改清单中的引导类路径以引用位置).
示例输出可能是:
Max stack frame size accummulated: 44140 Max stack depth: 1004
这有点粗糙,但对我来说是有效的.
注意:堆栈帧大小不是总堆栈大小(仍然不知道如何得到那个).实际上,线程堆栈有多种开销.我发现我通常需要2到3倍的报告堆栈最大帧大小作为-Xss值.呵呵,一定要做-Xss调试,而不需要代理加载,因为它增加了堆栈大小的要求.