Java – how to measure thread stack depth?

I have a 32 - bit Java service with scalability problem: the number of users is very high because of too many threads and insufficient memory In the long run, I plan to switch to 64 bit and reduce the number of threads per user In the short term, I want to reduce the stack size (- XSS, - XX: threadsacksize) to get more space But this is risky, because if I do it too small, I will get stackoverflowerrors

How do I measure the average and maximum stack size of my application to guide my decision to get the best - XSS value? I'm interested in two possible ways

>Measure the running JVM during integration testing Which analysis tools report the maximum stack depth? > Static analysis of applications, looking for deep levels of hierarchy Reflection on dependency injection makes this unlikely to work

Update: I know the long-term right way to solve this problem Pay attention to my question: how do I measure stack depth?

Update 2: I got a good answer to a related question about jpprofiler: can jpprofiler measure stack depth? (according to the community support suggestions of jpprofiler, I issued a separate question)

Solution

You can use aspects programmable as code to get the idea of stack depth (the load time Weaver allows you to recommend all load code except the system class loader) This aspect can solve all executed code, and can be paid attention to when calling methods and returning You can use it to capture most of the stack usage (you'll miss anything loaded from the system class loader, such as Java. *) Although not perfect, it avoids the need to change the code to collect stacktraceelement [], and can also import you into non JDK code that may not be written

For example (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
    }
}

An example stack might look like this:

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()

It's slow and has its own memory problems, but it allows you to get the required stack information

You can then use the max of each method in the stack trace_ Stack and Max_ Locals to calculate the frame size of this method (see class file format) Based on VM spec, I believe this should be the maximum frame size of (max_stack max_locales) * 4 bytes for the method (long / double occupies two entries on the operand stack / local vars and in max_stack and max_locals)

You can easily javap the class of interest. If there is not so much in the call stack, you can view the frame value Things like ASM provide you with simpler tools that you can use on a larger scale

Once calculated, you need to estimate the additional stack frames of the JDK class. You can call them at the maximum stack point and add them to the stack size It won't be perfect, but it should make you a good starting point for - XSS tuning without attacking around the JVM / JDK

Another note: I don't know what JIT / OSR does for frame size or stack requirements, so please note that - xvs adjustment may have different effects on cold or warm JVMs

Edit has several hours of downtime and throws another way This is a Java proxy that tracks the maximum stack frame size and stack depth This will enable you to use most JDK classes with other code and libraries, giving you better results than aspect weavers You need ASM V4 to work It's more for its fun, so it's fun under plinky Java, not profit

First, do something to track the stack frame size and depth:

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 );
    }
}

Next, let the Java proxy:

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 ();
            }
        } );
    }
}

The proxy class has a premain hook for instrumentation In that hook, it adds a transformer of type, which implements in stack frame size tracking It also adds proxies to the bootstrap class loader so that it can also handle JDK classes To do this, we need to retransmit anything that may have been loaded, such as string class. However, we must exclude various things used by agents or stack logs that cause infinite loops or other problems (some of which are found through repetition and errors) Finally, the agent adds a close hook to dump the results to stdout

public class Transformer implements ClassFileTransformer {

    @Override
    public byte[] transform ( ClassLoader loader,String className,Class<?> classBeing@R_419_1240@d,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;
    }
}

The transformer drives two separate transforms - one for calculating the maximum stack frame size for each method and one for recording the method It may work at once, but I don't want to use the ASM tree API or spend more time figuring it out

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 );
    }
}

The maxstack * visitor class handles the maximum stack frame size

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 );
    }
}

The callstack * visitor class handles the detection method of code with call stack frame records

Then you need a 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

Finally, add the following to the Java command line of the program you want to write:

-javaagent:path/to/Agent.jar

You also need to add asm-all-4.0 Jar and agent Jar (or change the boot class path in the manifest to reference the location)

Example output might be:

Max stack frame size accummulated: 44140
Max stack depth: 1004

It's a little rough, but it works for me

Note: the stack frame size is not the total stack size (still don't know how to get that) In fact, the thread stack has a variety of overhead I found that I usually need 2 to 3 times the maximum frame size of the report stack as the - XSS value Hehe, you must do - XSS debugging without agent loading, because it increases the requirement of stack size

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>