`
zhongxiucheng
  • 浏览: 68934 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

线程运行栈信息的获取

 
阅读更多

一、问题的引入
我们在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

}



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

分享到:
评论

相关推荐

    CreateThread创建多线程与单线程比较

    NULL // 不需要获得线程号码 ); CreatThread,它返回的是一个句柄;如果不需要再监视线程,则用CloseHandle()关闭线程句柄。 线程的函数必须定义为: DWORD WINAPI MyThreadProc(LPVOID pParameter); 下面演示多...

    linux系统编程之线程.zip

    参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。 参数4:线程主函数执行期间所使用的参数。 在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下...

    ThreadTracker:android的线程跟踪

    线程跟踪器Android线程追踪工具开发背景在Android中每个线程都在自己独立的栈中运行,我们虽然可以使用Thread.getAllStackTraces()获取到运行栈信息,却无法剖析器工具时,可能瞬间映入眼帘几百个线程,甚至很多都...

    C++多线程多进程.doc

    C++多线程多进程概述 C++多线程多进程是指在同一个程序中使用多个线程或进程来提高程序的并发度和响应时间。多线程多进程技术可以让程序在同一时间同时执行多条任务,...子进程获得父进程数据空间、堆和栈的复制品。

    深入理解高并发编程-Java线程池核心技术

    可以使当前线程让出 CPU 资源来让其他线程能够获取到 CPU 资源,进而能够执 行其他线程对应的任务,达到最大化利用 CPU 资源的目的。 线程的实现方式 在 Java 中,实现线程的方式大体上分为三种,通过继承 Thread ...

    MIPS将非X86架构嵌入式处理器推入GHz时代 新的MIPS S32 74K回归单线程、Broadcom率先获得授权.pdf

    MIPS将非X86架构嵌入式处理器推入GHz时代-New MIPS S32 74K回归单线程、Broadcom率先获得授权 MIPS公司近日宣布推出下一代基于创新嵌入式微架构的处理器内核系列,MIPS32 74K内核是业界第一款完全可合成的32位...

    高级java开发并发问题

    1.JVM为一个线程栈分配内存,该栈为每个线程方法调用保存一个栈帧 2.每一栈帧由一个局部变量数组、返回值、操作数堆栈和常量池组成 3.每个线程获得一个程序计数器,用于记录当前虚拟机正在执行的线程指令地址 4.系统...

    B站河北王校长-并发编程-深度核心面试知识汇总.pdf

    * 方法区则用来存放JVM加载的类、常量及静态变量等信息,也是线程共享的。 继承Thread类实现多线程 * 使用继承方式实现多线程的优点是,在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.current...

    Java并发编程(学习笔记).xmind

    如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务 threadFactory 创建线程的工厂 handler 拒绝策略 unit 是一个枚举,表示 ...

    Visual.C#.编程精彩百例

    实例20 获取车辆信息 实例21 简单角色类游戏的制作 实例22 旅馆住宿登记情况表制作 实例23 长命名空间的应用 实例24 文件特征计数 实例25 文本框输入数据的验证 第2篇 Visual C#中级编程实例 实例26 窗体...

    Multithreaded Programming in CVI

    其中,sAttrs是安全属性,stackSize是线程栈的大小,startAddress是线程的入口点,params是线程的参数,createFlags是线程的创建标志,threadId是线程的ID。 多线程编程考虑 在进行Multithreaded Programming时...

    百度2018校招AI异构计算工程师笔试题(第二批).pdf

    多进程里,子进程可获得父进程的部分堆与栈的数据;而线程会与同进程的其他线程共享数据,拥有自己的栈空间。线程的通信速度更快,切换更快,因为他们在同一地址空间内。线程使用公共变量/内存时不需要使用同步机制...

    代码客:G-Socket(IOCP) 1.0 (Server/Client)例程源码+Server体验程序+强大的压力测试工具

    (2)、处理线程:处理线程调用回调函数将信息传递给应用层或协议栈,可设定参数决定工作处理数量; (3)、看守线程:响应Accept事件调用AcceptEx,检测连接和心跳超时 ,将信息投递给工作线程,模块仅有一个看守线程...

    《Visual.C#.编程精彩百例》配套光盘.part2

    实例14 调用栈记录异常点 实例15 使用C#异常的栈跟踪 实例16 运行期间检测变量类型 实例17 常用值类型的原型定义 实例18 打印杨辉三角形 实例19 比较学生信息 实例20 获取车辆信息 ...

    《Visual.C#.编程精彩百例》配套光盘part1

    实例14 调用栈记录异常点 实例15 使用C#异常的栈跟踪 实例16 运行期间检测变量类型 实例17 常用值类型的原型定义 实例18 打印杨辉三角形 实例19 比较学生信息 实例20 获取车辆信息 ...

    Java诊断利器Arthas排查问题实践.pptx

    * thread:查看线程信息 * classloader:查看类加载器信息 * sc:查看类的实例信息 * watch:监控方法入参返回值 * trace:监控方法调用栈 * jad:实时反编译 * redefine:热更新类 * ognl:获取日志实现 Arthas ...

    java核心面试

    (1) 获取对象监视器的锁(lock) (2) 清空工作内存数据, 从主存复制对象成员变量到当前工作内存, 即同步数据 (read and load) (3) 执行代码,改变共享变量值 (use and assign) (4) 将工作内存数据刷回主存 (store ...

    java核心面试技术点

    (1) 获取对象监视器的锁(lock) (2) 清空工作内存数据, 从主存复制对象成员变量到当前工作内存, 即同步数据 (read and load) (3) 执行代码,改变共享变量值 (use and assign) (4) 将工作内存数据刷回主存 (store ...

    Java并发编程实战

    10.2.2 通过线程转储信息来分析死锁178 10.3 其他活跃性危险180 10.3.1 饥饿180 10.3.2 糟糕的响应性181 10.3.3 活锁181 第11章 性能与可伸缩性183 11.1 对性能的思考183 11.1.1 性能与可伸缩性184 11.1.2 ...

Global site tag (gtag.js) - Google Analytics