2011-11-30 22 views
9

Tôi có dịch vụ Java 32 bit với các vấn đề về khả năng mở rộng: với số lượng người dùng cao, chúng tôi hết bộ nhớ vì số lượng chuỗi quá lớn. Về lâu dài, tôi dự định chuyển sang 64-bit và giảm tỷ lệ chủ đề trên mỗi người dùng. Trong ngắn hạn, tôi muốn giảm kích thước ngăn xếp (-Xss, -XX: ThreadStackSize) để có thêm khoảng không. Nhưng điều này là nguy hiểm bởi vì nếu tôi làm cho nó quá nhỏ, tôi sẽ nhận được StackOverflowErrors.Làm thế nào tôi có thể đo chiều sâu ngăn xếp luồng?

Tôi làm cách nào để đo kích thước ngăn xếp trung bình và tối đa cho ứng dụng của mình để hướng dẫn quyết định về giá trị tối ưu -Xss? Tôi quan tâm đến hai phương pháp có thể:

  1. Đo JVM đang chạy trong khi thử nghiệm tích hợp. Công cụ định hình nào sẽ báo cáo độ sâu ngăn xếp tối đa?
  2. Phân tích tĩnh của ứng dụng tìm kiếm phân cấp cuộc gọi sâu. Phản ánh trong tiêm phụ thuộc làm cho nó không chắc rằng điều này sẽ làm việc.

Cập nhật: Tôi biết đúng cách lâu dài để khắc phục vấn đề này. Vui lòng tập trung vào câu hỏi tôi đã hỏi: làm cách nào để tôi đo độ sâu ngăn xếp?

Cập nhật 2: Tôi có một câu trả lời tốt đẹp trên một câu hỏi có liên quan cụ thể về JProfiler: Can JProfiler measure stack depth? (tôi đăng các câu hỏi riêng biệt theo đề nghị hỗ trợ cộng đồng JProfiler của)

+1

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. –

+2

@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. –

+0

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? –

Trả lời

6

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.

+0

Điểm yếu duy nhất mà tôi có thể nhìn thấy trong cách tiếp cận của bạn là nó sẽ không xử lý khi một ngoại lệ được ném từ một phương pháp tiếp tục xuống ngăn xếp. Điều này sẽ yêu cầu một khối thử/cuối cùng. – mchr

5

Tôi sẽ làm giảm các thiết lập -Xss trong một môi trường thử nghiệm cho đến khi bạn thấy một vấn đề. Sau đó thêm một số phòng đầu.

Giảm kích thước heap của bạn sẽ cung cấp cho ứng dụng của bạn nhiều không gian hơn cho ngăn xếp chuỗi.

Chỉ cần chuyển sang hệ điều hành 64 bit có thể cung cấp cho ứng dụng của bạn nhiều bộ nhớ hơn vì hầu hết các hệ điều hành 32 bit chỉ cho phép khoảng 1,5 GB cho mỗi ứng dụng. Tuy nhiên, ứng dụng 32 bit trên hệ điều hành 64 bit có thể sử dụng tới 3 -3,5 GB tùy thuộc vào hệ điều hành.

+0

Có, chúng tôi đã thử phương pháp này, nhưng ngay bây giờ thử nghiệm chỉ là nhị phân: chúng ta có được StackOverflowError hay không? Tôi muốn có một sự hiểu biết chi tiết hơn về việc sử dụng chồng thực tế của ứng dụng. Điểm tốt về kích thước heap, tôi đã quên điều đó. Có, 64-bit nằm trong kế hoạch dài hạn nhưng ứng dụng vẫn còn phụ thuộc gốc 32 bit cần làm việc. –

+2

+1 Mặc dù đây là một cách tiếp cận thử và lỗi, nó sẽ hoàn thành công việc. Nó quá tệ 'jvisualvm' không cung cấp thông tin như thế này. –

+1

Mặc dù bạn có mã gốc 32 bit, một hệ điều hành 64 bit sẽ cung cấp cho bạn nhiều bộ nhớ hơn ngay cả với một JVM 32 bit. –

3

Không có công cụ có thể sử dụng dễ dàng trong máy ảo Java để truy vấn độ sâu ngăn xếp theo byte. Nhưng bạn có thể đến đó. Dưới đây là một số gợi ý:

  • Ngoại lệ chứa mảng khung ngăn xếp cung cấp cho bạn các phương pháp được gọi.

  • Đối với mỗi phương pháp, bạn có thể tìm thấy the Code attribute trong tệp .class. Thuộc tính này chứa kích thước khung hình cho mỗi phương thức trong trường max_stack.

Vì vậy, những gì bạn cần là một công cụ biên dịch một HashMap chứa tên phương pháp + tên file + số dòng như phím và giá trị max_stack như các giá trị. Tạo một Throwable, tìm nạp các khung ngăn xếp từ nó với getStackTrace() và sau đó lặp lại qua StackTraceElement s.

Lưu ý:

Mỗi mục trên các toán hạng ngăn xếp có thể giữ một giá trị của bất kỳ Java loại máy ảo, trong đó có một giá trị kiểu dài hoặc gõ đúp.

Vì vậy, mỗi mục nhập ngăn xếp có thể là 64 bit, vì vậy bạn cần nhân max_stack với 8 để nhận byte.

Các vấn đề liên quan