0
点赞
收藏
分享

微信扫一扫

Flask 入门4:Flask 模板

紫荆峰 2024-02-01 阅读 14
jvmjava

Java虚拟机的组成

Java虚拟机主要分为以下几个组成部分:

正确打开字节码文件

推荐使用 jclasslib工具查看字节码文件。Github地址: https://github.com/ingokegel/jclasslib

字节码文件总共可以分为以下几个部分:

字节码基本信息

基本信息包含了jclasslib中能看到的两块内容: 

 Magic魔数

每个Java字节码文件的前四个字节是固定的,用16进制表示就是0xcafebabe。文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改不影响文件的内容。软件会使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。Java字节码文件中,将文件头称为magic魔数。Java虚拟机会校验字节码文件的前四个字节是不是0xcafebabe,如果不是,该字节码文件就无法正常使用,Java虚拟机会抛出对应的错误。

比如常见的文件格式校验方式如下:

主副版本号

主副版本号指的是编译字节码文件时使用的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2是46之后每升级一个大版本就加1;副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号1.2之后大版本号计算方法就是 : 主版本号 – 44,比如主版本号52就是JDK8。

 版本号的作用主要是判断当前类文件当编译时产生字节码的版本和运行时的JDK是否兼容。如果使用较低版本的JDK去运行较高版本JDK的字节码文件,无法使用会显示如下错误:

有两种方案:

 字节码常量池 

字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。如下图,常量池中定义了一个字符串,字符串的字面量值为123。

 属性名索引主要指向的字段的类型,常量值索引就是指向字符常量,这里面三个字段的常量值索引都指向abc这个字符串常量

 而指向的8号索引才最终指向27号真正的字符串,在后续的jvm的字符串常量池中会将8号这个string类型的索引入到StringTable中,故需要通过8号索引再找到27号索引,而不能在字段中直接指向27号索引这个字符串本身。

 如果我们将27号这个引用直接去掉,用8号引用直接存放这个字符串,不要27号这个引用的话,也是不可取的。因为我们定义的字段名可能与字符串名重复,这时候字段名就会直接指向字符串本身。如果没有这种间接引用,出现这种情况就会很难解决。

 常量池中的数据都有一个编号,编号从1开始。比如“我爱北京天安门”这个字符串,在常量池中的编号就是7。在字段或者字节码指令中通过编号7可以快速的找到这个字符串。

字节码指令中通过编号引用到常量池的过程称之为符号引用

方法区

字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。 

 通过分析方法的字节码指令,可以清楚地了解一个方法到底是如何执行的。先来看如下案例:

 这段代码编译成字节码指令之后是如下内容:

要理解这段字节码指令是如何执行的,我们需要先理解两块内存区域:操作数栈和局部变量表。

操作数栈是用来存放临时数据的内容,是一个栈式的结构,先进后出。

局部变量表是存放方法中的局部变量,包含方法的参数、方法中定义的局部变量,在编译期就已经可以确定方法有多少个局部变量。

1、iconst_0,将常量0放入操作数栈。此时栈上只有0。 

2、istore_1会从操作数栈中,将栈顶的元素弹出来,此时0会被弹出,放入局部变量表的1号位置。局部变量表中的1号位置,在编译时就已经确定是局部变量i使用的位置。完成了对局部变量i的赋值操作。 

 3、iload_1将局部变量表1号位置的数据放入操作数栈中,此时栈中会放入0。

 

4、iconst_1会将常量1放入操作数栈中。 

5、iadd会将操作数栈顶部的两个数据相加,现在操作数栈上有两个数0和1,相加之后结果为1放入操作数栈中,此时栈上只有一个数也就是相加的结果1。 

 6、istore_2从操作数栈中将1弹出,并放入局部变量表的2号位置,2号位置是j在使用。完成了对局部变量j的赋值操作。

7、return语句执行,方法结束并返回。

 

同理,我们可以自行分析下i++和++i的字节码指令执行的步骤。

i++的字节码指令如下,其中iinc 1 by 1指令指的是将局部变量表1号位置增加1,其实就实现了i++的操作。

而++i只是对两个字节码指令的顺序进行了更改:

 

面试题

 属性

属性主要指的是类的属性,比如源码的文件名、内部类的列表等。

 

 

举报

相关推荐

0 条评论