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不僅提供越來越多、越來越強的功能,而且暴露給用戶的控制方法越來越多,越來越強大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM