您的位置:首页 > 编程语言 > Java开发

使用JavaLangAccess和SharedSecrets来获取JVM中的实例

2016-11-02 11:22 453 查看

使用场景实例:

最近一段时间在mybatis框架的源码,看到框架的日志模块的时候,发现其集成了JDK的原生日志java.util.logging,于是我就去看了一下java.util.logging实现源码,在其中的LogRecord.java文件中看到其中有一段代码:

// Private method to infer the caller's class and method names
private void inferCaller() {
needToInferCaller = false;
JavaLangAccess access = SharedSecrets.getJavaLangAccess();
Throwable throwable = new Throwable();
int depth = access.getStackTraceDepth(throwable);

boolean lookingForLogger = true;
for (int ix = 0; ix < depth; ix++) {
// Calling getStackTraceElement directly prevents the VM
// from paying the cost of building the entire stack frame.
StackTraceElement frame =
access.getStackTraceElement(throwable, ix);
String cname = frame.getClassName();
boolean isLoggerImpl = isLoggerImplFrame(cname);
if (lookingForLogger) {
// Skip all frames until we have found the first logger frame.
if (isLoggerImpl) {
lookingForLogger = false;
}
} else {
if (!isLoggerImpl) {
// skip reflection call
if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
// We've found the relevant frame.
setSourceClassName(cname);
setSourceMethodName(frame.getMethodName());
return;
}
}
}
}
// We haven't found a suitable frame, so just punt.  This is
// OK as we are only committed to making a "best effort" here.
}


这个函数的目的是推断调用日志log的类,被public String getSourceClassName()方法使用。通常来说一个方法在调用log时一般都会显式的给当前类的信息作为log的输入参数,当没有给定的时候,就需要日志框架来自己推测,这时候就是用到了上面这个函数。当然这个推测不能保证找到的调用类实例一定正确。

SharedSecrets和JavaLangAccess的作用

当我们需要对使用Log的类名进行的推断的时候,我们就需要知道JVM里面的实例对象了,这时候我们就需要使用到SharedSecretsJavaLangAccess,通过这两个类来获取Java栈帧中存储的类信息,然后进行挑选,从而找出调用的类。

接下来看一下SharedSecrets和JavaLangAccess的使用方式:

测试环境:为了方便,在mybatis源码工程里面利用junit进行测试,创建新类testLang.java,也可以自己创建java工程进行测试。

测试代码:

package org.apache.ibatis.logging;

import sun.misc.JavaLangAccess;
import sun.misc.SharedSecrets;

/**
* Created by 茂升 on 2016/11/2.
*/
public class testLang {
public void testPrint() {
JavaLangAccess access = SharedSecrets.getJavaLangAccess();
Throwable throwable = new Throwable();

int depth = access.getStackTraceDepth(throwable);

//输出JVM栈帧中的所有类实例
for (int i = 0; i < depth; i++) {
StackTraceElement frame = access.getStackTraceElement(throwable, i);
System.out.println(frame);
}
}
}

在LogFactoryTest.java文件中添加方法,这里使用了junit:

@Test
public void yumsTest() {
//System.err.println("Yumaosheng TEST");
testLang t = new testLang();
t.testPrint();
}


执行结果:

org.apache.ibatis.logging.testLang.testPrint(testLang.java:12)
org.apache.ibatis.logging.LogFactoryTest.yumsTest(LogFactoryTest.java:52)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
org.junit.runners.ParentRunner.run(ParentRunner.java:309)
org.junit.runner.JUnitCore.run(JUnitCore.java:160)
com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Process finished with exit code 0


我们会发现代码输出了所有JVM栈帧中的实例对象的类名。也就是说我们使用JavaLangAccess和SharedSecrets可以获取栈帧中的所有实例对象的类名称,接下来我们需要剔除掉不可能是调用类的名字。

剔除不可能的调用类

首先日志框架自身是不会调用的,所以先判断当前在栈中找到的类是不是日志框架自身,使用isLoggerImplFrame()方法(这个方法比较简单就不做过多解释了)。然后我们还要排除在获取栈帧过程中和日志框架使用过程中的额外引入的类,像java.lang.reflect.*和sun.reflect.*这些类都是额外引入的类,不会是调用类。剔除这些类以后,我们就找到了调用log的类。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jdk jvm 源码 实例
相关文章推荐