服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

线程运行栈信息的获取

    一、问题的引入

    我们在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 < stack.length) {

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

* checkpermission method 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

}

扫描关注微信公众号