你可以得到類似的東西可被編織到您的代碼(加載時織允許建議除了系統類加載器加載的所有代碼)一個方面的堆棧深度的想法。這個方面可以解決所有執行的代碼,並且能夠記錄你何時調用方法以及何時返回。您可以使用它來捕獲大部分堆棧使用情況(您將錯過從系統類加載器加載的任何內容,例如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來計算方法的幀大小(請參閱class file format)。基於該vm spec我相信這應該是(max_stack + max_locals)*的最大幀大小的方法(長/雙佔據了操作數棧/本地變量兩個條目,並佔max_stack和max_locals)4字節。
如果您的調用堆棧中沒有那麼多,您可以輕鬆javap感興趣的類並查看幀值。而像asm這樣的東西爲您提供了一些簡單的工具,可以用於更大規模地實現此目的。
一旦你有了這個計算,你需要估計更多的堆棧幀的JDK類,可能會爲你在你的最大堆棧點被調用,並添加到您的堆棧大小。它不會是完美的,但它應該讓你在沒有黑客入侵JVM/JDK的情況下進行-Xss調整。其他
一注:我不知道是什麼JIT/OSR確實給幀大小或堆棧需求所以千萬要注意,你可能有從-Xss調整在一個寒冷的溫暖與JVM不同的影響。
編輯有幾個小時的停機時間,並把另一種方法扔在一起。這是一個java代理,它將檢測方法以跟蹤最大堆棧幀大小和堆棧深度。這將能夠最儀器的jdk類與其他代碼和庫一起,給你比方面編織更好的結果。您需要使用asm v4才能正常工作。它更多的是爲了它的樂趣,所以把它寫在plinking 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 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);
}
}
調用堆棧*訪客類處理插裝用代碼的方法調用堆棧幀記錄。
然後你需要爲agent.jar中一個MANIFEST.MF:
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相同的目錄中(或者更改清單中的Boot-Class-Path以引用該位置)。
樣本輸出可能是:
Max stack frame size accummulated: 44140
Max stack depth: 1004
這一切都有點粗糙,但對我的作品走了。
注意:堆棧幀大小不是總堆棧大小(仍然不知道如何獲得該堆棧大小)。實際上,線程堆棧有多種開銷。我發現我通常需要報告的堆棧最大幀大小的2到3倍作爲-Xss值。哦,一定要在不加載代理的情況下進行-Xss調優,因爲它會增加您的堆棧大小要求。
您應該考慮切換到異步模式。在系統中擁有比CPU核更多的線程沒有任何意義。 – 2011-11-30 15:47:37
@ VladLazarenko - 同意。正如我所說,從長遠來看,我打算減少每用戶線程數,但我需要儘快解決。 –
您創建了多少個線程?您如何管理它們?爲什麼你需要一個每用戶線程,你不能重用線程,並提供線程每請求? –