http://www.iteye.com/topic/13179
线程运行栈信息的获取
一、问题的引入
我们在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时代的作品。我们来看相关源代码。
1. /** 2. Instantiate location information based on a Throwable. We 3. expect the Throwable <code>t</code>, to be in the format 4. <pre> 5. java.lang.Throwable 6. ... 7. at org.apache.log4j.PatternLayout.format(PatternLayout.java:413); 8. at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183); 9. at org.apache.log4j.Category.callAppenders(Category.java:131); 10. at org.apache.log4j.Category.log(Category.java:512); 11. at callers.fully.qualified.className.methodName(FileName.java:74); 12. ... 13. </pre> 14. */ 15. public LocationInfo(Throwable t, String fqnOfCallingClass); { 16. String s; 17. … 18. t.printStackTrace(pw);; 19. s = sw.toString();; 20. sw.getBuffer();.setLength(0);; 21. …. // 这里的代码省略 22. }
这里我们可以看到整体的实现思路。
首先,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 caller's 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")) // We've found the relevant frame. … // 这里的代码省略 } // We haven't 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 <code>t</code>, to be in the format <pre> 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) ... </pre> <p>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. * * <p>If there is a security manager, and this thread is not * the current thread, then the security manager's * <tt>checkPermission</tt> method is called with a * <tt>RuntimePermission("getStackTrace")</tt> permission * to see if it's ok to get the stack trace. * * <p>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 <tt>StackTraceElement</tt>, * 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<Thread, StackTraceElement[]> getAllStackTraces() { // check for getStackTrace permission // Get a snapshot of the list of all threads }
六、总结
从总的发展趋势来看,JDK不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。
发表评论
-
Consistent Hashing
2010-02-09 17:12 805import java.util.Collection; ... -
防止JAVA程序重复启动的一个另类解决办法
2009-03-31 20:29 1943http://www.iteye.com/topic/3773 ... -
简单LRU算法实现缓存
2009-03-27 11:31 1564http://www.blogjava.net/killme2 ... -
MappedByteBuffer内存映射
2009-03-15 21:26 2153通过把一个套接字通道(SocketChannel)注册到一个选 ... -
JAVA中操作数据库方式与设计模式的应用
2009-03-11 15:02 848http://www.iteye.com/topic/1981 ... -
java调用Oracle EXP备忘
2009-03-10 11:28 1556http://www.blogjava.net/BlueDav ... -
关于java中volatile字段的ordering
2009-03-08 22:00 778多个volatile操作之间是有序的,compiler和处理 ... -
Initialize-on-demand Holder Class
2009-03-02 15:01 936public class Singleton { ... -
Interceptor的实现
2009-03-01 21:26 813public interface Action { p ... -
Quartz CronTrigger最完整配置说明
2009-02-15 15:12 1003CronTrigger配置格式: 格 ... -
ClassLoader介绍
2009-01-21 13:24 809JVM在运行时会产生三个ClassLoader,Bootstr ... -
消息的发送与回调
2009-01-06 22:03 820/** * 回调接口 * @author ... -
自己编写IOC
2009-01-05 21:59 1188<?xml version="1.0&qu ... -
四则运算的中缀转后缀
2008-12-11 11:41 1622import java.math.BigDecimal ... -
一个简单的多线程、断点下载Java程序
2008-12-10 20:49 1905//这个是任务Bean public cl ... -
生产者-消费者
2008-12-02 14:05 824package debug; import java.u ...
相关推荐
这种调用方式的一个限制是不支持可变参数列表,因为被调用者无法预先知道调用者究竟压入了多少参数。 ### __fastcall `__fastcall`调用约定试图通过减少栈操作来提高函数调用的效率,尤其是在需要频繁传递参数的...
这是为了让易语言知道如何正确地调用这些外部函数。 3. **调用函数**:在程序适当的位置,使用易语言的调用语句来执行LIB或OBJ中的函数。易语言提供了一些关键字,如`调用标准函数`或`调用动态链接库函数`,用于...
在这种约定中,参数从右向左压入堆栈,调用者负责清理堆栈。这意味着调用函数必须知道参数的数量和类型,以便正确地弹出参数。__cdecl的一个关键特性是它可以处理可变参数列表,如printf函数。函数名在编译后不添加...
这种方法可以通过检查调用者的身份、证书或者使用特定的密钥来实现。 2. **加密导出函数名**:对导出函数的名称进行加密或哈希处理,使得第三方无法通过名字直接查找和调用函数。在运行时,只有知道解密方法的应用...
首先,RPC的核心思想是将远程调用过程透明化,使得开发者可以像调用本地方法一样调用远程服务。这种抽象简化了分布式系统的设计和开发。在Java中,实现RPC通常包括以下几个关键步骤: 1. **定义服务接口**:RPC调用...
对于stdcall,参数由被调用者清理;而对于cdecl,参数由调用者清理。在Windows下,大部分API函数使用stdcall约定,而C语言函数通常使用cdecl约定。因此,当你知道DLL函数的调用约定时,可以使用`ctypes.windll`或`...
这里的"Cdecl"调用约定是指C风格的函数调用约定,它要求参数按照声明顺序压栈,由被调用者清理栈。如果你的C++代码使用了其他调用约定(如stdcall),需要相应调整DllImport的设置。 在`TestCallDll`这个例子中,...
5. **请求解包(Request Unpacking)**:服务端接收到消息后,进行解包,恢复出原始的方法调用信息。 6. **执行服务(Service Execution)**:服务提供者执行相应的方法,并获取结果。 7. **结果打包(Result ...
调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。 回调函数在实际中有很多应用场景,例如,在库中提供了某些排序算法的实现,如冒泡排序、...
在Java开发中,Webservice动态调用是一项常见的技术,主要用于实现服务消费者和服务提供者之间的松耦合通信。本篇将详细介绍如何进行Webservice动态调用,并以CXF框架为例进行演示。 首先,我们要理解什么是动态...
5. **播放Flash内容**:通过调用对象的特定方法,如`Play`,启动Flash的播放。 6. **处理事件**:若需要监听Flash播放器的事件,如播放结束、暂停等,也需要使用API函数注册事件处理函数。 7. **错误处理**:在...
客户端需要知道服务端的位置(例如,IP地址和端口号),并且能够将方法调用转换为网络消息发送给服务端。 3. **服务端模块(Server)**: 服务端是接收并处理RPC请求的程序。它实现了API模块中定义的接口,并提供...
4. **实例化并调用服务**:在客户端代码中,创建stub类的实例,然后通过实例调用服务的方法,传递参数并获取返回值。 5. **处理响应**:服务的响应通常以XML形式返回,客户端需要解析这个响应,并根据业务逻辑进行...
这些代理类封装了服务调用的所有细节,使得开发者可以像调用本地方法一样调用Web服务。 4. **使用`SoapHttpClientProtocol`类**:在较旧版本的.NET Framework中,如.NET Framework 2.0,可以使用`System.Web....
在Web服务中,服务提供者定义一组接口,这些接口通过WSDL(Web服务描述语言)文档对外发布,客户端则通过解析WSDL找到调用服务的方法。 在Web服务的实现中,源码分析是理解其工作流程的关键。以BORLAND的实现为例,...
RPC使得分布式系统中的组件能够像调用本地方法一样调用远程服务,极大地简化了分布式编程。 在Java中,RPC框架的实现通常涉及到以下关键概念和技术: 1. **接口与代理**:RPC的核心是通过接口来定义服务。客户端...
4. 请求与响应的对象:RequestDispatcher.forward 方法的调用者与被调用者之间共享相同的 request 对象和 response 对象,而 HttpServletResponse.sendRedirect 方法调用者与被调用者使用各自的 request 对象和 ...
这可以通过调用`PrinterJob.getPrinterJob()`静态方法实现。 ```java import java.awt.print.PrinterJob; PrinterJob job = PrinterJob.getPrinterJob(); ``` 2. **设置PrintService** `PrinterJob`对象...
1. **添加引用**:在调用DLL的工程中,引入刚刚创建的DLL头文件,这将使编译器知道如何声明和调用DLL中的函数。 2. **链接DLL**:将DLL的.lib文件添加到调用工程的链接器输入项,这样编译器就知道在哪里寻找DLL的...
调用者包含一个命令对象,并在适当的时候调用命令的`execute`方法。调用者不需要了解命令的具体细节,它只需要知道如何调用命令。 4. 接收者(Receiver):实际执行命令操作的对象。它知道如何执行具体的操作,命令...