zoukankan      html  css  js  c++  java
  • Java高级--Java线程运行栈信息的获取 getStackTrace()

    我们在Java程序中使用日志功能(JDK Log或者Log4J)的时候,会发现Log系统会自动帮我们打印出丰富的信息,格式一般如下:为了免去解析StackTrace字符串的麻烦,JDK1.4引入了一个新的类,StackTraceElement。

      一、问题的引入

      我们在Java程序中使用日志功能(JDK Log或者Log4J)的时候,会发现Log系统会自动帮我们打印出丰富的信息,格式一般如下:

      [运行时间] [当前类名] [方法名]

      INFO: [用户信息]

      具体例子如Tomcat启动信息:

      Jul 9, 2004 11:22:41 AM org.Apache.coyote.http11.Http11Protocol start

      INFO: Starting Coyote HTTP/1.1 on port 8080

      看起来这毫无神奇之处,不就是打印了一条信息吗?但如果好奇心重一点,追寻后面的实现原理,会发现这确实很神奇。

      上面的Log信息的[当前类名] [方法名]部分 不是用户自己添加的,而是Log系统自动添加的。这意味着Log系统能够自动判断当前执行语句是哪个类的哪个方法。这是如何做到的?

      我们翻遍java.LANg.reflection package,幻想着找到一个Statement语句级别的Reflection类,通过这个Statement对象获得Method,然后通过这个 Method获得declared Class。这不就获得对应的Class和Method信息了吗?这是一个不错的构想,但也只能是一个构想;因为没有这个Statement对象。

      再想一下。对了,Java不是有一个Thread类吗?Thread.currentThread() 方法获取当前线程,我们能不能通过这个当前线程获取当前运行的Method和Class呢?很遗憾,如果你还在用JDK1.4或以下版本,那么找不到这样 的方法。(JDK1.5的情况后面会讲)

      再想一下。对了,我们都有很深刻的印象,当系统抛出Exception的时候,总是打印出一串的信息, 告诉我们Exception发生的位置,和一层一层的调用关系。我们也可以自己调用Exception的printStackTrace()方法来打印这 些信息。这不就是当前线程运行栈的信息吗?找到了,就是它。

      Exception的printStackTrace()方法继承自Throwable,那么我们来看一下,JDK的Throwable的printStackTrace()方法是如何实现的。

      我们先来看JDK1.3的源代码,会发现Throwable.printStackTrace()方法调用了一个native printStackTrace0()方法。我们找不到任何线索,可以用在我们自己的Java代码中。

      那怎么办?Throwable.printStackTrace()的输出结果字符串里面不是包含了当前线程运行栈的所有信息吗?我们可以从这个字符串中抽取自己需要的信息。JDK1.3的时代,也只能这么做了。

      二、Log4J 1.2的相关实现

      Log4J 1.2是JDK1.3时代的作品。我们来看相关源代码。

      [code]

      /**

      Instantiate location information based on a Throwable. We

      expect the Throwable t, to be in the format

      java.lang.Throwable

      ...

      at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)

      at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)

      at org.apache.log4j.Category.callAppenders(Category.java:131)

      at org.apache.log4j.Category.log(Category.java:512)

      at callers.fully.qualifIEd.className.methodName(FileName.java:74)

      ...

      */

      public LocationInfo(Throwable t, String fqnOfCallingClass) {

      String s;

      …

      t.printStackTrace(pw);

      s = sw.toString();

      sw.getBuffer().setLength(0);

      …. // 这里的代码省略

      }

      [/code]

      这里我们可以看到整体的实现思路。

      首先,t.printStackTrace(pw); 获得stack trace字符串。这个t是 new Throwable()的结果。用户程序调用Log4J方法之后,Log4J自己又进行了4次调用,然后才获得了 t = new Throwable() :

      at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)

      at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)

      at org.apache.log4j.Category.callAppenders(Category.java:131)

      at org.apache.log4j.Category.log(Category.java:512)

      那么,往下走4行,就可以回到用户程序本身的调用信息:

      at callers.fully.qualified.className.methodName(FileName.java:74)

      这一行里面,类名、方法名、文件名、行号等信息全有了。解析这一行,就可以获得需要的所有信息。

      三、JDK1.4 Log的相关实现

      Log4J大获成功,Sun决定在JDK1.4中引入这个Log功能。

      为了免去解析StackTrace字符串的麻烦,JDK1.4引入了一个新的类,StackTraceElement。

      public final class StackTraceElement implements java.io.Serializable {

      // Normally initialized by VM (public constructor added in 1.5)

      private String declaringClass;

      private String methodName;

      private String fileName;

      private int lineNumber;

      可以看到,恰好包括类名、方法名、文件名、行号等信息。

      我们来看JDK1.4 Log的相关实现。

      LocationInfo.java 的infoCaller方法(推算调用者)

      // Private method to infer the callers class and method names

      private void inferCaller() {

      …

      // Get the stack trace.

      StackTraceElement stack[] = (new Throwable()).getStackTrace();

      // First, search back to a method in the Logger class.

      …. // 这里的代码省略

      // Now search for the first frame before the "Logger" class.

      while (ix

      StackTraceElement frame = stack[ix];

      String cname = frame.getClassName();

      if (!cname.equals("java.util.logging.Logger"))

      // Weve found the relevant frame.

      … // 这里的代码省略

      }

      // We haven found a suitable frame, so just punt. This is

      // OK as we are only committed to making a "best effort" here.

      }

      从注释中就可以看出实现思路。过程和Log4J异曲同工。只是免去了解析字符串的麻烦。

      四、Log4J 1.3 Alpha的相关实现

      既然JDK1.4中引入了StackTraceElement类,Log4J也要与时俱进。LocationInfo类也有了相应的变化。

      /**

      Instantiate location information based on a Throwable. We

      expect the Throwable t, to be in the format

      java.lang.Throwable

      ...

      at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)

      at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)

      at org.apache.log4j.Category.callAppenders(Category.java:131)

      at org.apache.log4j.Category.log(Category.java:512)

      at callers.fully.qualified.className.methodName(FileName.java:74)

      ...

      However, we can also deal with JIT compilers that "lose" the

      location information, especially between the parentheses.

      */

      public LocationInfo(Throwable t, String fqnOfInvokingClass) {

      if(PlatformInfo.hasStackTraceElement()) {

      StackTraceElementExtractor.extract(this, t, fqnOfInvokingClass);

      } else {

      LegacyExtractor.extract(this, t, fqnOfInvokingClass);

      }

      }

      可以看到,Log4J首先判断Java平台是否支持StackTraceElement,如果是,那么用StackTraceElementExtractor,否则使用原来的LegacyExtractor。

      下面来看StackTraceElementExtractor.java

      /**

      * A faster extractor based on StackTraceElements introduced in JDK 1.4.

      *

      * The present code uses reflection. Thus, it should compile on all platforms.

      *

      * @author Martin Schulz

      * @author Ceki Gülcü

      *

      */

      public class StackTraceElementExtractor {

      protected static boolean haveStackTraceElement = false;

      private static Method getStackTrace = null;

      private static Method getClassName = null;

      private static Method getFileName = null;

      private static Method getMethodName = null;

      private static Method getLineNumber = null;

      …. // 以下代码省略

      可以看到,Log4J 1.3仍然兼容JDK1.3,而且为JDK1.4也做了相应的优化。

      五、JDK1.5的Thread Stack Trace

      JDK1.5在Thread类里面引入了getStackTrace()和getAllStackTraces()两个方法。这下子,我们不用 (new Throwable()).getStackTrace ();可以调用

      Thread.getCurrentThread().getStackTrace()来获得当前线程的运行栈信息。不仅如此,只要权限允许,还可以获得其它线程的运行栈信息。

      /**

      * Returns an array of stack trace elements representing the stack dump

      * of this thread. This method will return a zero-length array if

      * this thread has not started or has terminated.

      * If the returned array is of non-zero length then the first element of

      * the array represents the top of the stack, which is the most recent

      * method invocation in the sequence. The last element of the array

      * represents the bottom of the stack, which is the least recent method

      * invocation in the sequence.

      *

      *

      If there is a security manager, and this thread is not

      * the current thread, then the security managers

      * checkPermissionmethod is called with a

      * RuntimePermission("getStackTrace")permission

      * to see if its ok to get the stack trace.

      *

      *

      Some virtual Machines may, under some circumstances, omit one

      * or more stack frames from the stack trace. In the extreme case,

      * a virtual machine that has no stack trace information concerning

      * this thread is permitted to return a zero-length array from this

      * method.

      *

      * @return an array of StackTraceElement,

      * each represents one stack frame.

      *

      * @since 1.5

      */

      public StackTraceElement[] getStackTrace() {

      if (this != Thread.currentThread()) {

      // check for getStackTrace permission

      SecurityManager security = System.getSecurityManager();

      if (security != null) {

      security.checkPermission(

      SecurityConstants.GET_STACK_TRACE_PERMISSION);

      }

      }

      if (!isAlive()) {

      return EMPTY_STACK_TRACE;

      }

      Thread[] threads = new Thread[1];

      threads[0] = this;

      StackTraceElement[][] result = dumpThreads(threads);

      return result[0];

      }

      /**

      * Returns a map of stack traces for all live threads.

      *

      * @since 1.5

      */

      public static Map getAllStackTraces() {

      // check for getStackTrace permission

      // Get a snapshot of the list of all threads

      }

      六、总结

      从总的发展趋势来看,JDK不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。

  • 相关阅读:
    IP应用加速技术详解:如何提升动静混合站点的访问速率?
    阿里云PolarDB发布重大更新 支持Oracle等数据库一键迁移上云
    BigData NoSQL —— ApsaraDB HBase数据存储与分析平台概览
    洛谷P1457 城堡 The Castle
    洛谷P1461 海明码 Hamming Codes
    洛谷P1460 健康的荷斯坦奶牛 Healthy Holsteins
    洛谷P1459 三值的排序 Sorting a Three-Valued Sequence
    洛谷P1458 顺序的分数 Ordered Fractions
    洛谷P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib
    洛谷P1215 [USACO1.4]母亲的牛奶 Mother's Milk
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/3661709.html
Copyright © 2011-2022 走看看