Bạn có thể có ý tưởng về chiều sâu ngăn xếp với một thứ gì đó có thể được dệt vào mã của bạn (trình tải thời gian để cho phép thông báo tất cả mã đã tải trừ bộ nạp lớp hệ thống). Các khía cạnh sẽ làm việc xung quanh tất cả các mã thực hiện và sẽ có thể lưu ý khi bạn đang gọi một phương pháp và khi bạn quay trở lại. Bạn có thể sử dụng điều này để nắm bắt phần lớn mức sử dụng ngăn xếp của mình (bạn sẽ bỏ lỡ bất kỳ thứ gì được tải từ trình tải lớp hệ thống, ví dụ: java. *). Mặc dù không hoàn hảo, nó tránh phải thay đổi mã của bạn để thu thập StackTraceElement [] tại các điểm mẫu và cũng đưa bạn vào mã không phải jdk mà bạn có thể chưa viết.
Ví dụ (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
}
}
Và một mẫu chồng có thể như:
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()
Rất chậm và có vấn đề về bộ nhớ riêng của mình nhưng có thể là hoàn toàn khả thi để giúp bạn có được thông tin chồng bạn cần.
Sau đó, bạn có thể sử dụng max_stack và max_locals cho mỗi phương pháp trong ngăn xếp dấu vết để tính toán kích thước khung hình (xem class file format) cho phương pháp. Dựa trên vm spec Tôi tin rằng điều này nên là (max_stack + max_locals) * 4bytes cho kích thước khung tối đa cho một phương thức (dài/đôi chiếm hai mục trên chồng toán hạng/vars cục bộ và được tính trong max_stack và max_locals).
Bạn có thể dễ dàng javap các lớp quan tâm và xem các giá trị khung nếu bạn không có nhiều trong ngăn xếp cuộc gọi của bạn. Và một cái gì đó như asm cung cấp cho bạn một số công cụ dễ sử dụng để thực hiện điều này trên quy mô lớn hơn.
Khi bạn đã tính toán này, bạn cần ước tính các khung ngăn xếp bổ sung cho các lớp JDK có thể được bạn gọi tại các điểm xếp chồng tối đa của bạn và thêm vào các kích thước ngăn xếp của bạn. Nó sẽ không hoàn hảo nhưng nó nên giúp bạn có được một điểm khởi đầu tốt cho việc điều chỉnh -Xss mà không cần hack xung quanh JVM/JDK.
Một lưu ý khác: Tôi không biết JIT/OSR làm gì để khung kích thước hoặc yêu cầu ngăn xếp nên lưu ý rằng bạn có thể có các tác động khác nhau từ -Xữ chỉnh trên JVM lạnh và ấm.
CHỈNH SỬA có một vài giờ nghỉ ngơi và cùng nhau đưa ra một cách tiếp cận khác. Đây là một tác nhân java sẽ có phương pháp cụ để theo dõi kích thước khung stack tối đa và độ sâu ngăn xếp. Điều này sẽ có thể thiết bị hầu hết các lớp jdk cùng với các mã và thư viện khác của bạn, mang lại cho bạn kết quả tốt hơn so với trình biên tập khía cạnh. Bạn cần asm v4 để làm việc này. Đó là nhiều hơn cho những niềm vui của nó để tập tin này dưới plinking java cho vui, không phải lợi nhuận.
Trước tiên, hãy điều gì đó để theo dõi kích thước stack frame và chiều sâu:
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);
}
}
Tiếp theo, làm cho các đại lý 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();
}
});
}
}
Lớp đại lý có móc premain cho thiết bị đo đạc. Trong móc đó, nó thêm một biến áp lớp mà các công cụ trong theo dõi kích thước khung stack. Nó cũng bổ sung thêm tác nhân vào trình nạp lớp khởi động để nó có thể xử lý các lớp jdk. Để làm điều đó, chúng ta cần phải truyền lại bất cứ thứ gì có thể được tải, như String.class.Tuy nhiên, chúng ta phải loại trừ một loạt những thứ được sử dụng bởi các đại lý hoặc đăng nhập ngăn xếp dẫn đến vòng vô hạn hoặc các vấn đề khác (một số trong đó đã được tìm thấy bằng cách thử và sai). Cuối cùng, tác nhân thêm một hook shutdown để kết xuất các kết quả tới 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;
}
}
Biến áp điều khiển hai kích thước riêng biệt - một để tìm ra kích thước khung tối đa cho mỗi phương pháp và phương pháp để ghi lại phương pháp ghi. Nó có thể được thực hiện trong một lần nhưng tôi không muốn sử dụng API cây ASM hoặc dành nhiều thời gian hơn để tìm ra nó.
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);
}
}
Các lớp khách truy cập MaxStack * xử lý tìm ra kích thước khung tối đa.
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 * Lớp khách truy cập xử lý phương pháp thiết bị có mã để gọi ghi nhật ký khung ngăn xếp.
Và sau đó bạn cần một MANIFEST.MF cho Agent.jar:
Manifest-Version: 1.0
Premain-Class: phil.agent.Agent
Boot-Class-Path: asm-all-4.0.jar
Can-Retransform-Classes: true
Cuối cùng, thêm dòng sau vào dòng lệnh java của bạn cho chương trình bạn muốn cụ:
-javaagent:path/to/Agent.jar
Bạn cũng sẽ cần phải có asm-all-4.0.jar trong cùng thư mục với Agent.jar (hoặc thay đổi Boot-Class-Path trong tệp kê khai để tham khảo vị trí).
Một sản lượng mẫu có thể là:
Max stack frame size accummulated: 44140
Max stack depth: 1004
Đây là tất cả một chút thô nhưng làm việc cho tôi để có được đi.
Lưu ý: kích thước khung ngăn xếp không phải là tổng kích thước ngăn xếp (vẫn không thực sự biết cách nhận kích thước đó). Trong thực tế, có một loạt các chi phí cho ngăn xếp chủ đề. Tôi thấy rằng tôi thường cần khoảng từ 2 đến 3 lần kích thước khung hình tối đa của ngăn xếp được báo cáo dưới dạng giá trị -Xss. Oh, và chắc chắn để làm điều chỉnh -Xss mà không có tác nhân được nạp vì nó thêm vào yêu cầu kích thước ngăn xếp của bạn.
Bạn nên xem xét chuyển sang mô hình không đồng bộ. Nó không làm cho bất kỳ ý nghĩa để có nhiều chủ đề hơn bạn có lõi CPU trong hệ thống. –
@VladLazarenko - đã đồng ý. Như tôi đã nói, về lâu dài, tôi dự định giảm tỷ lệ luồng cho mỗi người dùng, nhưng tôi cần sửa chữa nhanh hơn sớm hơn. –
Bạn tạo bao nhiêu chủ đề và bạn quản lý chúng như thế nào? Và tại sao bạn cần một chủ đề cho mỗi người dùng, bạn không thể sử dụng lại các chủ đề và cung cấp luồng cho mỗi yêu cầu? –