栈的存取速度低于堆,jvm控制线程堆栈大小的参数
一、问题介绍
当我们在Java程序中使用日志功能(JDK日志或Log4J)时,会发现日志系统会自动为我们打印出丰富的信息,格式一般如下:
[运行时间][当前类名][方法名]
信息:[用户信息]
例如Tomcat启动信息:
2004年7月9日上午11:22:41 org . Apache . coyote . http 11 . http 11协议开始
信息:在端口8080上启动Coyote HTTP/1.1
这似乎没什么神奇的。不就是印个信息吗?但如果你更好奇,追求后来的实现原理,你会发现这真的很神奇。
以上日志信息的[当前类名][方法名]部分不是用户自己添加的,而是日志系统自动添加的。这意味着日志系统可以自动确定当前执行的语句是哪个类的哪个方法。这是怎么做到的?
我们翻遍了java.lang.Reflection包,幻想着找到一个语句级的反射类,通过这个语句对象得到方法,然后通过这个方法得到声明的类。难道没有得到相应的类和方法信息吗?这是个好主意,但只能是个想法;因为没有语句对象。
再想想。对了,Java不是有线程类吗?方法获取当前线程。可以通过这个当前线程得到当前运行的方法和类吗?遗憾的是,如果你还在用JDK1.4或以下版本,就找不到这样的方法了。(JDK1.5的情况后面再说)
再想想。对了,我们都很感动。当系统抛出异常时,总是会打印出一串信息,告诉我们异常的位置以及一层一层的调用关系。我们也可以自己调用Exception的printStackTrace()方法来打印这些信息。这不就是当前线程运行栈的信息吗?在这里。就是这样。
Exception的printStackTrace()方法继承自Throwable,所以我们来看看JDK的Throwable printStackTrace()方法是如何实现的。
我们先来看看JDK1.3的源代码,我们会发现Throwable.printStackTrace()方法调用了一个原生的printStackTrace0()方法。我们找不到任何可以用在自己Java代码中的线索。
然后呢?Throwable.printStackTrace()的输出结果字符串不是包含了当前线程运行栈的所有信息吗?我们可以从这个字符串中提取我们需要的信息。在JDK1.3时代,我们只能这样。
二、Log4J 1.2的相关实现
Log4J 1.2是JDK1.3时代的作品。我们来看看相关的源代码。
[代码]
/**
基于Throwable实例化位置信息。我们
预计可抛出的t,格式为
java.lang.Throwable
.
位于org . Apache . log4j . pattern layout . format(pattern layout . Java:413)
位于org . Apache . log4j . file appender . do append(file appender . Java:183)
位于org . Apache . log4j . category . callappenders(category . Java:131)
位于org . Apache . log4j . category . log(category . Java:512)
at callers . fully . qualified . class name . method name(filename . Java:74)
.
*/
public LocationInfo(Throwable t,String fqnOfCallingClass) {
字符串s;
…
t . printstacktrace(pw);
s=SW . tostring();
sw.getBuffer()。setLength(0);
.//此处省略代码。
}
[/code]
这里可以看到整体的实现思路。
起初是t . printstacktrace(pw);获取堆栈跟踪字符串。这是new Throwable()的结果。在用户程序调用Log4J方法之后,Log4J本身又进行了四次调用,然后得到t=new Throwable():
位于org . Apache . log4j . pattern layout . format(pattern layout . Java:413)
位于org . Apache . log4j . file appender . do append(file appender . Java:183)
位于org . Apache . log4j . category . callappenders(category . Java:131)
位于org . Apache . log4j . category . log(category . Java:512)
然后,向下4行,您可以返回到用户程序本身的调用信息:
at callers . fully . qualified . class name . method name(filename . Java:74)
在这一行中,类名、方法名、文件名、行号等信息都有。通过解析这一行,您可以获得您需要的所有信息。
三、JDK1.4Log的实现。
Log4J大获成功,孙决定在JDK1.4中引入这个原木功能。
为了免去解析堆栈跟踪字符串的麻烦,JDK1.4引入了一个新的类,StackTraceElement。
公共最终类StackTraceElement实现java.io.Serializable {
//通常由伏特计初始化(在1.5中添加了公共构造函数)
私有字符串声明类;
私有字符串方法名
私有字符串文件名;
专用内线号码
可以看到,恰好包括类名、方法名、文件名、行号等信息。
我们来看JDK1.4日志的相关实现。
LocationInfo.java的信息呼叫者方法(推算调用者)
//私有方法来推断调用方的类名和方法名
私有空的推断调用方(){
…
//获取堆栈跟踪。
stack trace element stack[]=(new Throwable()).getStackTrace();
//首先,搜索回记录器类中的一个方法。
….//这里的代码省略
//现在搜索"记录器"类之前的第一帧。
while (ix stack.length) {
stack trace element frame=stack[IX];
字符串cname=frame。获取类名();
如果(!cname。equals( Java。util。伐木。logger ))
//我们已经找到了相关的框架。
… //这里的代码省略
}
//我们还没有找到合适的框架,所以就踢吧。这是
//好的,因为我们在这里只是承诺"尽最大努力"。
}
从注释中就可以看出实现思路。过程和Log4J异曲同工。只是免去了解析字符串的麻烦。
四、Log4J 1.3 alpha的相关实现
既然JDK1.4中引入了StackTraceElement类,Log4J也要与时俱进位置信息.类也有了相应的变化。
/**
基于可投掷的实例化位置信息。我们
预计可抛出的t,格式为
java.lang.Throwable
.
位于org。阿帕奇。log4j。图案布局。格式(图案布局。Java:413)
位于org。阿帕奇。log4j。文件附加器。做附加(文件附加器。Java:183)
位于org。阿帕奇。log4j。类别。callappenders(类别。Java:131)
位于org。阿帕奇。log4j。类别。日志(类别。Java:512)
在呼叫者处。完全地。合格。类名。方法名(文件名。Java:74)
.然而,我们也可以处理"丢失"了
位置信息,尤其是在括号之间。
*/
public LocationInfo(Throwable t,String fqnOfInvokingClass) {
如果(平台信息。hasstacktraceelement()){
stacktraceelementextractor。extract(this,t,fqnOfInvokingClass);
}否则{
LegacyExtractor.extract(this,t,fqnOfInvokingClass);
}
}
可以看到,Log4J首先判断爪哇平台是否支持StackTraceElement,如果是,那么用StackTraceElementExtractor,否则使用原来的LegacyExtractor
下面来看StackTraceElementExtractor.java
/**
*基于JDK 1.4中引入的StackTraceElements的更快的提取器。
*
*当前代码使用反射。因此,它应该可以在所有平台上编译。
*
* @作者马丁舒尔茨
* @作者切基居尔库
*
*/
公共类StackTraceElementExtractor {
受保护的静态布尔haveStackTraceElement=false
私有静态方法getStackTrace=null
私有静态方法getClassName=null
私有静态方法getFileName=null
私有静态方法getMethodName=null
私有静态方法getLineNumber=null
….//以下代码省略
可以看到,Log4J 1.3仍然兼容JDK1.3,而且为JDK1.4也做了相应的优化。
五、JDK1.5的线程堆栈跟踪
JDK1.5在线类里面引入了getStackTrace()和getAllStackTraces()两个方法。这下子,我们不用(new Throwable()).getStackTrace();可以调用
Thread.getCurrentThread().getStackTrace()来获得当前线程的运行栈信息。不仅如此,只要权限允许,还可以获得其它线程的运行栈信息。
/**
*返回表示堆栈转储的堆栈跟踪元素数组
*本线程。如果满足以下条件,此方法将返回零长度数组
*该线程尚未启动或已终止。
*如果返回的数组长度不为零,则
*数组表示堆栈的顶部,这是最新的
*序列中的方法调用。数组的最后一个元素
*表示堆栈的底部,这是最近的方法
*序列中的调用。
*
*
如果有安全管理器,而这个线程没有
*当前线程,然后是安全管理器
*检查权限方法是用
*运行时权限( getStackTrace )权限
*查看是否可以获取堆栈跟踪。
*
*
在某些情况下,一些虚拟机可能会忽略一个
*堆栈跟踪中的一个或多个堆栈帧。在极端情况下,
*虚拟机没有堆栈跟踪信息,涉及
*允许该线程从此返回零长度数组
*方法。
*
* @返回StackTraceElement数组,
*每个代表一个堆栈帧。
*
* @从1.5开始
*/
公共堆栈跟踪元素[]getstack trace(){
如果(这个!=Thread.currentThread()) {
//检查getStackTrace权限
安全管理器安全=系统。getsecuritymanager();
如果(安全!=null) {
安全。支票许可证(
安全常量. GET _ STACK _ TRACE _ permit);
}
}
如果(!isAlive()) {
返回空堆栈跟踪;
}
线程[]线程=新线程[1];
threads[0]=this;
StackTraceElement[][]result=dump threads(线程);
返回结果[0];
}
/**
*返回所有活动线程的堆栈跟踪图。
*
* @从1.5开始
*/
公共静态映射getAllStackTraces() {
//检查getStackTrace权限
//获取所有线程列表的快照
}
六、总结
从总的发展趋势来看,JDK不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。