网站定制公司kinglink,企业推广方式隐迅推知名,费用,广东深圳今天最新通知Java虚拟机的组成
Java虚拟机主要分为以下几个组成部分#xff1a; 类加载子系统#xff1a;核心组件类加载器#xff0c;负责将字节码文件中的内容加载到内存中。 运行时数据区#xff1a;JVM管理的内存#xff0c;创建出来的对象、类的信息等等内容都会放在这块区域中。…Java虚拟机的组成
Java虚拟机主要分为以下几个组成部分 类加载子系统核心组件类加载器负责将字节码文件中的内容加载到内存中。 运行时数据区JVM管理的内存创建出来的对象、类的信息等等内容都会放在这块区域中。 执行引擎包含了即时编译器、解释器、垃圾回收器执行引擎使用解释器将字节码指令解释成机器码使用即时编译器优化性能使用垃圾回收器回收不再使用的对象。 本地接口调用本地使用C/C编译好的方法本地方法在Java中声明时都会带上native关键字如下图所示。 正确打开字节码文件
推荐使用 jclasslib工具查看字节码文件。Github地址 https://github.com/ingokegel/jclasslib
字节码文件总共可以分为以下几个部分 基础信息魔数、字节码文件对应的Java版本号、访问标识(public final等等)、父类和接口信息 常量池 保存了字符串常量、类或接口名、字段名主要在字节码指令中使用 字段 当前类或接口声明的字段信息 方法 当前类或接口声明的方法信息核心内容为方法的字节码指令 属性 类的属性比如源码的文件名、内部类的列表等 字节码基本信息
基本信息包含了jclasslib中能看到的两块内容 Magic魔数
每个Java字节码文件的前四个字节是固定的用16进制表示就是0xcafebabe。文件是无法通过文件扩展名来确定文件类型的文件扩展名可以随意修改不影响文件的内容。软件会使用文件的头几个字节文件头去校验文件的类型如果软件不支持该种类型就会出错。Java字节码文件中将文件头称为magic魔数。Java虚拟机会校验字节码文件的前四个字节是不是0xcafebabe如果不是该字节码文件就无法正常使用Java虚拟机会抛出对应的错误。
比如常见的文件格式校验方式如下 主副版本号
主副版本号指的是编译字节码文件时使用的JDK版本号主版本号用来标识大版本号JDK1.0-1.1使用了45.0-45.3JDK1.2是46之后每升级一个大版本就加1副版本号是当主版本号相同时作为区分不同版本的标识一般只需要关心主版本号。1.2之后大版本号计算方法就是 : 主版本号 – 44比如主版本号52就是JDK8。 版本号的作用主要是判断当前类文件当编译时产生字节码的版本和运行时的JDK是否兼容。如果使用较低版本的JDK去运行较高版本JDK的字节码文件无法使用会显示如下错误 有两种方案 1.升级JDK版本将图中使用的JDK6升级至JDK8即可正常运行容易引发其他的兼容性问题并且需要大量的测试。 2.将第三方依赖的版本号降低或者更换依赖以满足JDK版本的要求。建议使用这种方案 字节码常量池
字节码文件中常量池的作用避免相同的内容重复定义节省空间。如下图常量池中定义了一个字符串字符串的字面量值为123。 比如在代码中编写了两个相同的字符串“我爱北京天安门”字节码文件甚至将来在内存中使用时其实只需要保存一份此时就可以将这个字符串以及字符串里边包含的字面量放入常量池中以达到节省空间的作用。 String str1 我爱北京天安门; String str2 我爱北京天安门; 属性名索引主要指向的字段的类型常量值索引就是指向字符常量这里面三个字段的常量值索引都指向abc这个字符串常量 而指向的8号索引才最终指向27号真正的字符串在后续的jvm的字符串常量池中会将8号这个string类型的索引入到StringTable中故需要通过8号索引再找到27号索引而不能在字段中直接指向27号索引这个字符串本身。 如果我们将27号这个引用直接去掉用8号引用直接存放这个字符串不要27号这个引用的话也是不可取的。因为我们定义的字段名可能与字符串名重复这时候字段名就会直接指向字符串本身。如果没有这种间接引用出现这种情况就会很难解决。 常量池中的数据都有一个编号编号从1开始。比如“我爱北京天安门”这个字符串在常量池中的编号就是7。在字段或者字节码指令中通过编号7可以快速的找到这个字符串。
字节码指令中通过编号引用到常量池的过程称之为符号引用。 方法区
字节码中的方法区域是存放字节码指令的核心位置字节码指令的内容存放在方法的Code属性中。 通过分析方法的字节码指令可以清楚地了解一个方法到底是如何执行的。先来看如下案例 int i 0; int j i 1; 这段代码编译成字节码指令之后是如下内容 要理解这段字节码指令是如何执行的我们需要先理解两块内存区域操作数栈和局部变量表。
操作数栈是用来存放临时数据的内容是一个栈式的结构先进后出。
局部变量表是存放方法中的局部变量包含方法的参数、方法中定义的局部变量在编译期就已经可以确定方法有多少个局部变量。
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只是对两个字节码指令的顺序进行了更改 面试题 问int i 0; i i; 最终i的值是多少 答答案是0我通过分析字节码指令发现i先把0取出来放入临时的操作数栈中 接下来在临时数据表中对i进行加1i变成了1最后再将之前保存的临时值0重新赋值到临时数据表中的i最后i就变成了0。 属性
属性主要指的是类的属性比如源码的文件名、内部类的列表等。