java输出当前执行的行号

前几天有人在论坛上问”Java不抛错的情况,如何定位到函数所在源代码行数”,觉得这个问题很有意思,所以就试着查看java类库的源码,看看到底是怎么实现的。虽然最后不能确定是怎么实现的,但是还是有意外的收获的,那就是Java程序输出当前执行的行号。

关于问题

相信用Java的人对下面这用异常情况的输出格式都不陌生

Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 10at 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类{(String[] args) {test1();}(){StackTraceElement[] trace = new Throwable().getStackTrace();// 下标为0的元素是上一行语句的信息, 下标为1的才是调用printLine的地方的信息StackTraceElement tmp = trace[1];System.out.println( tmp.getClassName() + “.” + tmp.getMethodName()+ “(” + tmp.getFileName() + “:” + tmp.getLineNumber() + “)” );}(){printLine();}(){printLine();test();}}使用Thread类

后来上网搜了一下,发现还有这种方法,顺便也贴出来吧:

{(String[] args) {test1();}(){StackTraceElement[] trace = Thread.currentThread().getStackTrace();// 注意!这里是下标为2的,而不是为1的StackTraceElement tmp = trace[2];System.out.println( tmp.getClassName() + “.” + tmp.getMethodName()+ “(” + tmp.getFileName() + “:” + tmp.getLineNumber() + “)” );}(){printLine();}(){printLine();test();}}

这里有两个地方很奇怪:

使用new Throwable().getStackTrace()时,调用堆栈并不包含getStackTrace()函数的堆栈信息。而使用Thread.currentThread().getStackTrace()时,调用堆栈却包含了getStackTrace()的堆栈信息。难道是因为Throwable会直接返回,而Thread还会有一些其他处理?或者是因为他们依赖的不是同一个方法? 注:Thread依赖于StackTraceElement[][] dumpThreads(Thread[] threads);第二点,,Thread.currentThread().getStackTrace()的堆栈中第一条是java(Unknown Source)

为什么无法获得行号呢?虽然javac的-g:none选项会产生不包含调试信息的.class文件,也就是不没有行号信息。但是%JAVA_HOME%\jre\lib\rt.jar文件中的.class文件是有调试信息的,也就是说有行号信息。经过测试发现,由于一些设置,我的java使用的是独立安装的jre中的rt.jar文件,而这个jar文件中并没有行号信息。 上面就是回复这个帖子的全部收获,感觉虽然花了好多时间,但是收获还是蛮大的。

写于2015/04/23

乐观者在灾祸中看到机会;悲观者在机会中看到灾祸

java输出当前执行的行号

相关文章:

你感兴趣的文章:

标签云: