java输出当前执行的行号
来源:互联网 发布:c语言switch用法 编辑:程序博客网 时间:2024/06/05 22:59
前几天有人在论坛上问”Java不抛错的情况,如何定位到函数所在源代码行数”,觉得这个问题很有意思,所以就试着查看java类库的源码,看看到底是怎么实现的。虽然最后不能确定是怎么实现的,但是还是有意外的收获的,那就是Java程序输出当前执行的行号。
关于问题
相信用Java的人对下面这用异常情况的输出格式都不陌生
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10 at Test.print(Test.java:13) at Test.test(Test.java:8) at Test.main(Test.java:4)
首先是异常类型,数组越界,空指针引用等等。然后是方法调用堆栈,包括文件名及行号。那么异常是怎么获得文件及行号的?
解决过程(未解决…)
面对这个问题,我首先想到的是printStackTrace()
方法,毕竟比较常用吗。然后去查Exception
类的源码(竟然不知道这个方法是从Throwable
继承过来的…)。
之后发现是Throwable
的方法,便查看了Throwable.printStackTrace()
方法,跟着调用链走,可以发现在Throwable.printStackTrace(PrintStream s)
中有这几行代码(我的是源码是Java JDK 1.8.0 build 40附带的)
s.println(this);StackTraceElement[] trace = getOurStackTrace();for (StackTraceElement traceElement : trace) s.println("\tat " + traceElement);
可以发现这段代码就是输出异常信息的代码。包括下面显示的函数调用堆栈: at 包.类.方法(文件:行号)。 那么让我们看看 getOurStackTrace()
方法,其中又有这么一段代码:
stackTrace = new StackTraceElement[depth];for (int i=0; i < depth; i++) stackTrace[i] = getStackTraceElement(i);...return stackTrace;
那么,可以确定getStackTraceElement(int i)
可以获得第i层函数调用的信息:包括文件和行号。顺藤摸瓜,我们看看getStackTraceElement(int i)
是怎么实现的:
native StackTraceElement getStackTraceElement(int index);
好吧,线索就是到这儿断的。。。
我的猜测
额,这应该是我第一次见native
关键字,然后查了一下该关键字的作用,参见java官方文档和百度百科
一个Native Method就是一个Java调用非Java代码的接口。一个Native Method是这样一个Java的方法:该方法的实现由非Java语言实现,比如C或C++。
然后,我又想起来前一段时间看的一篇博客:原文(英语),译文。这里面介绍了javap的用法。
javap是一个Java类文件反汇编程序,可以查看Java编译器生成的字节码,是分析代码的一个好工具。
如果我们执行javap -help
或者直接执行javap
查看javap的帮助信息,就会发现其中有这么一行:
-l 输出行号和本地变量表
然后,我就写了一个异常怪异的程序:有的好几条语句一行,有的好几行一条语句。编译,反编译。发现输出的行号非常准确。也就是说.class文件里是有行号信息的。
OK, 我们再回到getStackTraceElement(int i)
方法上,它既然不是用java写的,而是用其他语言,那么我们能不能猜测它其实是调用了java.exe(或者java.exe依赖的某个.dll文件,以下简称java.exe)中的一个隐藏接口呢?
既然java程序是在java.exe上执行的(正常执行时), 那么执行堆栈应该也只有java.exe知道吧,至于行号,java.exe完全可以从.class文件中读取。那么,我们猜测getStackTraceElement
方法调用了java.exe中的某个隐藏借口也是可以的吧。那么,调试时呢?那就只能从jdb.exe获取行号信息。为了统一、方便,我觉得很可能是从某个.dll文件中的某个函数中获取的。
限于本人不会反汇编C程序。就只能到这里了…..
Java获取行号源码
使用Throwable
类
public class PrintLine{ public static void main(String[] args) { test1(); } public static void printLine(){ StackTraceElement[] trace = new Throwable().getStackTrace(); // 下标为0的元素是上一行语句的信息, 下标为1的才是调用printLine的地方的信息 StackTraceElement tmp = trace[1]; System.out.println( tmp.getClassName() + "." + tmp.getMethodName() + "(" + tmp.getFileName() + ":" + tmp.getLineNumber() + ")" ); } public static void test(){ printLine(); } public static void test1(){ printLine(); test(); }}
使用Thread
类
后来上网搜了一下,发现还有这种方法,顺便也贴出来吧:
public class PrintLine{ public static void main(String[] args) { test1(); } public static void printLine(){ StackTraceElement[] trace = Thread.currentThread().getStackTrace(); // 注意!这里是下标为2的,而不是为1的 StackTraceElement tmp = trace[2]; System.out.println( tmp.getClassName() + "." + tmp.getMethodName() + "(" + tmp.getFileName() + ":" + tmp.getLineNumber() + ")" ); } public static void test(){ printLine(); } public static void test1(){ printLine(); test(); }}
这里有两个地方很奇怪:
- 使用
new Throwable().getStackTrace()
时,调用堆栈并不包含getStackTrace()
函数的堆栈信息。而使用Thread.currentThread().getStackTrace()
时,调用堆栈却包含了getStackTrace()
的堆栈信息。难道是因为Throwable
会直接返回,而Thread
还会有一些其他处理?或者是因为他们依赖的不是同一个方法?
注:Thread
依赖于
private native static StackTraceElement[][] dumpThreads(Thread[] threads);
- 第二点,
Thread.currentThread().getStackTrace()
的堆栈中第一条是
java.lang.Thread.getStackTrace(Unknown Source)
为什么无法获得行号呢?虽然javac的-g:none
选项会产生不包含调试信息的.class文件,也就是不没有行号信息。但是%JAVA_HOME%\jre\lib\rt.jar
文件中的.class文件是有调试信息的,也就是说有行号信息。经过测试发现,由于一些设置,我的java使用的是独立安装的jre中的rt.jar文件,而这个jar文件中并没有行号信息。
上面就是回复这个帖子的全部收获,感觉虽然花了好多时间,但是收获还是蛮大的。
写于2015/04/23
- java输出当前执行的行号
- Android/Java获取当前代码执行时所在的文件名/方法名/行号
- JavaScript使用console.log输出当前的行号
- Lua 输出函数名称 和 当前行号
- Log4j是输出日志时是如何获知当前方法、行号的
- c#获取当前代码运行的文件名、运行的函数名以及当前代码执行的行号
- smarty中foreach输出自增变量(当前行号)
- C++获得当前执行代码在源文件中的行号
- listCtrl 获取当前选中的行号
- 计算QPlaintTextEdit当前光标(cursor)的行号
- Java中获取当前运行代码的类名、方法名、行号
- Java中获取当前运行代码的类名、方法名、行号
- Java中获取当前运行代码的类名、方法名、行号
- Java中获取当前运行代码的类名、方法名、行号
- 打印当前源文件的文件名、当前行号、时间、年月日
- 打印出当前文件的文件名和当前行号
- Log 使用以及输出行号的方法
- paip.java c++得到当前类,方法名称以及行号
- JAVA变量及内存分配
- ubuntu建立多级目录命令
- Ubuntu下安装TURN Server (rfc5766-turn-server)
- android手势操作滑动效果触摸屏事件处理
- [LeetCode] Reverse Nodes in k-Group
- java输出当前执行的行号
- 程序数据库管理器不匹配;请检查安装解决
- yuv h264 avi WindowsMediaPlayer播放成功,虽然时间很短
- std::sort函数中的陷阱
- 万恶的console.log()
- MySQL自带函数整理与使用
- apk反编译工具操作汇总
- mysql密码忘记,该怎么办
- #、##和__VA_ARGS__