写在前面
源码 。
本文一起看下方法调用相关的指令invokexxx以及方法返回(栈帧弹出线程栈)相关的指令xReturn 。
1:正文
因为invokexxx指令和普通的指令不同,会创建一个新的栈帧,并压倒操作数栈中,所以我们首先需要来定义一个公共的创建栈帧的方法来让ivvokestatic,invokeinterface等使用,如下:
public class MethodInvokeLogic {
public static void invokeMethod(Frame invokerFrame, Method method) {
if (method.name.equalsIgnoreCase("returnALong")) {
System.out.println("------invoke xxxx 指令,做如下的事情;------");
System.out.println("1:从当前栈帧中获取对应的执行线程");
System.out.println("2:为要执行的方法创建对应的栈帧,并将栈帧压倒线程栈,作为当前栈帧");
System.out.println("3:如果是有入参的话,则从调用栈帧的操作数栈中弹出入参,并设置到新栈帧的局部变量表中,这样被调用方法执行时就可以通过load指令从局部变量中获取操作");
}
Thread thread = invokerFrame.thread();
Frame newFrame = thread.newFrame(method);
thread.pushFrame(newFrame);
// 将方法需要的参数设置到新栈帧的局部变量表中
int argSlotCount = method.argSlotCount();
if (argSlotCount > 0) {
for (int i = argSlotCount - 1; i >= 0; i--) {
Slot slot = invokerFrame.operandStack().popSlot();
newFrame.localVars().setSlot(i, slot);
}
}
//hack
if (method.isNative()) {
if ("registerNatives".equals(method.name())) {
thread.popFrame();
} else {
throw new RuntimeException("native method " + method.name());
}
}
}
}
这里我们以invokestatic指令和lreturn指令为例来看下对应的模拟代码,invokestatic指令:
public class INVOKE_STATIC extends InstructionIndex16 {
@Override
public void execute(Frame frame) {
RunTimeConstantPool runTimeConstantPool = frame.method().clazz().constantPool();
MethodRef methodRef = (MethodRef) runTimeConstantPool.getConstants(this.idx);
Method resolvedMethod = methodRef.ResolvedMethod();
if (!resolvedMethod.isStatic()) {
throw new IncompatibleClassChangeError();
}
Class clazz = resolvedMethod.clazz();
// 确保初始化完成(加载,链接,初始化中的初始化,即类加载的最后一步)
if (!clazz.initStarted()) {
frame.revertNextPC();
ClassInitLogic.initClass(frame.thread(), clazz);
return;
}
// 执行方法(即生成栈帧并压入到线程栈)
MethodInvokeLogic.invokeMethod(frame, resolvedMethod);
}
}
lreturn:
public class LRETURN extends InstructionNoOperands {
@Override
public void execute(Frame frame) {
System.out.println("------lreturn 指令执行,做如下的事情:------");
System.out.println("1:弹出当前的方法栈帧");
System.out.println("2:获取上一个方法");
System.out.println("3:从当前方法的操作数栈中获取执行结果,并推送到上一个方法的操作数栈中");
Thread thread = frame.thread();
Frame currentFrame = thread.popFrame();
Frame invokerFrame = thread.topFrame();
long val = currentFrame.operandStack().popLong();
// 获取上一个方法,并将结果压倒其操作数栈的栈顶,这样,上一个就可以通过pop指令获取结果
invokerFrame.operandStack().pushLong(val);
}
}
main类:
/**
* -Xthejrepath D:\programs\javas\java1.8/jre -Xthetargetclazz D:\test\itstack-demo-jvm-master\tryy-too-simulate-classload-load-clazz\target\test-classes\org\itstack\demo\test\HelloWorld
*/
public class Main {
public static void main(String[] args) {
Cmd cmd = Cmd.parse(args);
if (!cmd.ok || cmd.helpFlag) {
System.out.println("Usage: <main class> [-options] class [args...]");
return;
}
if (cmd.versionFlag) {
//注意案例测试都是基于1.8,另外jdk1.9以后使用模块化没有rt.jar
System.out.println("java version \"1.8.0\"");
return;
}
startJVM(cmd);
}
private static void startJVM(Cmd cmd) {
// 创建classpath
Classpath cp = new Classpath(cmd.thejrepath, cmd.classpath);
// System.out.printf("classpath:%s class:%s args:%s\n", cp, cmd.getMainClass(), cmd.getAppArgs());
System.out.printf("classpath:%s parsed class:%s \n", cp, cmd.thetargetclazz);
//获取className
// String className = cmd.getMainClass().replace(".", "/");
try {
// byte[] classData = cp.readClass(className);
/*byte[] classData = cp.readClass(cmd.thetargetclazz.replace(".", "/"));
System.out.println(Arrays.toString(classData));
System.out.println("classData:");
for (byte b : classData) {
//16进制输出
System.out.print(String.format("%02x", b & 0xff) + " ");
}*/
// 创建类加载器准备加载类
/**
* 加载3个阶段
* 1:加载
* 找到字节码,并将其存储到原元空间(<=7方法区),然后该类,该类父类,父接口也加载并在堆中生成对应的Class对象
* 2:链接
* 验证:验证文件内容的合法性,如是否cafebabe打头,结构是否符合定义
* 准备:主要是给静态变量申请内存空间,以及赋初始值,如int,short这种则给默认值0
* 解析:符号引用(指向类或者方法的一个字符串)转换为直接引用(jvm的内存地址)
* 3:初始化
* 执行<init>,<clinit>方法,完成静态变量的赋值
*/
ClassLoader classLoader = new ClassLoader(cp);
String clazzName = cmd.thetargetclazz.replace(".", "/");
Class mainClass = classLoader.loadClass(clazzName);
Method mainMethod = mainClass.getMainMethod();
new Interpreter(mainMethod, true);
/*// 创建className对应的ClassFile对象
ClassFile classFile = loadClass(clazzName, cp);
MemberInfo mainMethod = getMainMethod(classFile);
if (null == mainMethod) {
System.out.println("Main method not found in class " + cmd.classpath);
return;
}
// 核心重点代码:通过解释器来执行main方法
new Interpreter(mainMethod);*/
} catch (Exception e) {
System.out.println("Could not find or load main class " + cmd.getMainClass());
e.printStackTrace();
}
}
/**
* 获取main函数,这里我们要模拟是执行器执行main函数的过程,当然其他方法也是一样的!!!
* @param classFile
* @return
*/
private static MemberInfo getMainMethod(ClassFile classFile) {
if (null == classFile) return null;
MemberInfo[] methods = classFile.methods();
for (MemberInfo m : methods) {
if ("main".equals(m.name()) && "([Ljava/lang/String;)V".equals(m.descriptor())) {
return m;
}
}
return null;
}
/**
* 生成class文件对象
* @param clazzName
* @param cp
* @return
*/
private static ClassFile loadClass(String clazzName, Classpath cp) {
try {
// 获取类class对应的byte数组
byte[] classData = cp.readClass(clazzName);
return new ClassFile(classData);
} catch (Exception e) {
System.out.println("无法加载到类: " + clazzName);
return null;
}
}
}
定义需要加载的类:
public class HelloWorld {
public static void main(String[] args) {
/* long x = fibonacci(10);
System.out.println(x);*/
returnALong();
}
public static long returnALong() {
long longResult = 99;
return longResult;
}
//斐波那契数列(Fibonacci sequence)
/*private static long fibonacci(long n) {
if (n <= 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}*/
}
定义main的program argument:
-Xthejrepath
D:\programs\javas\java1.8/jre
-Xthetargetclazz
D:\test\itstack-demo-jvm-master\tryy-too-simulate-invokexxx-and-xreturn\target\test-classes\org\itstack\demo\test\HelloWorld
最后运行:
写在后面
参考文章列表
JVM 虚拟机字节码指令表 。
jvm方法调用指令invokestatic,invokespecial,invokeinterface,invokevirutal分析 。