我有一个具有可伸缩性问题的32位Java服务:由于用户计数高,我们由于线程数过多而耗尽了内存。从长远来看,我计划切换到64位,并降低线程/用户的比率。在短期内,我想减少堆栈大小(-Xss,-XX:ThreadStackSize),以获得更多的空间。但这是有风险的,因为如果我把它弄得太小,我就会得到StackOverflowErrors。
如何度量我的应用程序的平均和最大堆栈大小,以指导我对最优-Xss值的决策?是我感兴趣的两种可能的方法:
Update:我知道解决这个问题的长期正确方法.请集中注意我已经问过的问题:如何测量堆栈深度?
更新2:我在一个相关问题上得到了一个很好的答案,特别是关于JProfiler:JProfiler能测量堆栈深度吗? (我根据JProfiler的社区支持建议发布了单独的问题)
发布于 2011-12-01 18:10:20
您可以通过一个可以编织到代码中的方面来了解堆栈深度(loader,允许通知除系统类加载器以外的所有加载代码)。这个方面将围绕所有已执行的代码工作,并且能够在调用方法和返回时记录。您可以使用它来捕获大部分堆栈使用情况(您将错过从系统类加载器加载的任何内容,例如java.*)。虽然并不完美,但它避免了更改代码以在示例点收集StackTraceElement[],还可以将您带入非jdk代码,而您可能还没有编写这些代码。
例如(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来计算该方法的框架大小(参见类文件格式)。基于vm规范,我认为对于方法的最大帧大小,这应该是(max_stack+max_locals)*4字节(long/double占用操作数堆栈/本地vars上的两个条目,并在max_stack和max_locals中计算)。
如果调用堆栈中没有那么多内容,您可以轻松地对感兴趣的类进行javap,并查看框架值。像asm这样的东西为您提供了一些简单的工具,可以在更大的范围内实现这一点。
一旦计算了这个值,就需要估计JDK类的额外堆栈帧,这些帧可能由您在最大堆栈点调用,并将其添加到堆栈大小中。它并不完美,但它应该为-Xss调优提供一个不错的起点,而不必对JVM/JDK进行黑客攻击。
另一个注意事项:我不知道JIT/OSR对框架大小或堆栈需求做了什么,所以一定要注意,您可能对冷热JVM上的-Xss调优产生不同的影响。
编辑有几个小时的停机时间,并提出了另一种方法。这是一个java代理,它将对方法进行测试,以跟踪最大堆栈帧大小和堆栈深度。这将能够与其他代码和库一起对大多数jdk类进行测试,从而给出比方面编织器更好的结果。您需要asm v4才能工作。它更多的是为了它的乐趣,所以把这个文件下为乐趣,而不是利润。
首先,做一些跟踪堆栈帧大小和深度的事情:
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 ();
}
} );
}
}代理类有用于检测的前主钩子。在这个挂钩中,它添加了一个类转换器,用于堆栈帧大小跟踪中的仪器。它还将代理添加到引导类加载器中,以便它也可以处理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 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 );
}
}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最后,将以下内容添加到您想要测试的程序的java命令行中:
-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调优,因为它增加了堆栈大小的要求。
发布于 2011-11-30 15:56:19
我会减少测试环境中的-Xss设置,直到发现问题为止。那就增加一些头部空间。
减少堆大小将为应用程序提供更多的线程堆栈空间。
仅仅切换到64位操作系统就可以给应用程序更多的内存,因为大多数32位OSes只允许每个应用程序大约1.5GB,但是64位操作系统上的32位应用程序根据操作系统的不同最多可以使用3-3.5GB。
发布于 2011-12-01 14:01:09
Java中没有可用的工具来查询堆栈深度(以字节为单位)。但你可以到那里。以下是一些提示:
.class。此属性包含字段max_stack中每个方法的帧大小。因此,您需要的是一个编译HashMap的工具,它包含方法名称+文件名+行号作为键,值max_stack作为值。创建一个Throwable,用getStackTrace()从它获取堆栈帧,然后在StackTraceElement上迭代。
注意:
操作数堆栈上的每个条目都可以保存任何Java虚拟机类型的值,包括long或type double类型的值。
因此,每个堆栈条目可能是64位,因此需要将max_stack与8相乘才能获得字节。
https://stackoverflow.com/questions/8328679
复制相似问题