通过JVM字节码文件发现的一些小知识点
我们以这个程序去探讨
package JVM;
public class Compile {
public static int data = 22;
public String compile(String engStr){
String chnStr = "";
if(engStr.equals("Hello World!"))
chnStr = "你好世界!";
return chnStr;
}
public static void main(String[] args) {
String engStr ="Hello World!";
Compile compile = new Compile();
String chnStr = compile.compile(engStr);
System.out.println(chnStr);
}
}
看问题1或者2的时候看一下下面这个参考表:
指令 | 含义 |
---|---|
stack=2 | 表示操作数栈的深度为2 |
locals=3 | 表示本地变量的个数为3 |
args_size=2 | 表示本地参数数量为2 |
1、操作数栈是在什么时候确定的?
答案:是在java文件编译的时候就确定的。
当我们通过javap -v Compile.class反汇编一个java字节码文件时,会有如下图中所示:
所以,通过stack=2就可以确定操作数栈在java代码编译的时候就已经确定了。
2、为什么我们在实例方法中可以调用this呢?
答案:因为我们的构造方法已经隐式的传入了this这个参数,将它放在本地变量的第一个,虽然在代码中不可见。
当我们截出构造函数反编译码的时候,会发现它的本地变量数量为1,如下图所示:
这个变量就是java底层为我们隐式传入的一个this变量,它可以用来调用实例本身。
3、常量池究竟是一个什么东西?
答案:常量池分为静态常量池和运行时常量池,静态常量池相当于我们整个类在执行的时候的一个资源仓库,而运行时常量池就是用到哪个在静态常量池里面去哪个。
下面解释一下静态常量池,通过反编译的代码我们就可以看出,常量池中除了我们平常在代码中所见到的字符串常量之外还有类的路径信息,返回值信息,行号表(LineNumberTable),本地变量表等等,基本这个类在整个加载执行的过程中用到的所有信息都在里面,这个类的常量池中应该有58个常量,这里我只截了一部分,如下图所示:
4、java中一个类最多能实现多少接口?
答案:最多可以实现65536个接口。
在一个java类的字节码文件中,例如下面这个图,这里的每个数字都是16进制,其中有两个字节表示这个类实现接口的数量,两个字节,总共四个数字,而且还是16进制,所以可以表示的最大数字为0xFFFF<==>65536
5、为什么我们在编译代码的时候会抛出我们的哪一行代码没有通过编译?为什么会直接抛出行号?
我们都知道,底层操作系统能识别的是例如上一张图的.class字节码文件,既然他是在识别这个文件,那么他为什么可以知道是我们源码代码中哪一行的代码出现了问题?其实在我们的字节码文件里有一个行号表,它翻译成一个json文件的话,格式是这个样子的,如下图所示:
在图中我们可以看出,行号表底部有一个行号信息,.class字节码文件中的每一行都对应着源码的每一行,所以,操作系统底层才会识别出是哪一行源码出现了问题,然后将行号返回至控制台。