JVM虚拟机入门到实战(持续更新中)
基础篇-初识JVM
JVM本质上是一个运行在计算机上的程序,它的职责是运行java字节码文件。
写好的java文件先由javac编译为class文件,但class文件上的指令是无法直接在计算机中运行的,这是就要把class文件交由java虚拟机(JVM)解释,即转换为机器码,最后在计算机中运行文件。
JVM的功能
- 解释和运行
- 对字节码文件中的指令实时的解释称机器码,让计算机执行
- 内存管理
- 自动为对象、方法等分配内存
- 自动的垃圾回收机制,回收不再使用的对象
- 即时编译
- 对热点代码进行优化,提升执行效率
JVM的功能-即时编译
Java语言如果不做任何优化,性能是不如C,C++等语言的。
Java语言需要将java文件先编译再实时解释为机械码才能交由计算机运行。而C/C++等语言在编译的时候就能直接转换为机械码文件进而直接由计算机执行。
Java进行实时解释主要是为了实现跨平台特性。而C和C++则不支持跨平台。
即时编译会在JVM将文件转换为机械码后将其保存到内存中去,等再次要执行时就能直接调用内存中的文件去执行。这优化了Java的性能,使其追赶上C和C++等语言。
常见的JVM
字节码文件详解
Java虚拟机的组成
- 类加载器ClassLoader:加载class字节码文件中的内容到内存中去。
- 运行时数据区域(JVM管理的内存):负责管理JVM使用到的内存,比如创建对象和销毁对象。
- 执行引擎:将字节码文件中的指令解释为机器码,同时使用即时编译器优化性能。
- 本地接口:调用本地已经编译的方法,比如蓄奴籍中提供方C/C++的方法。
本地接口执行引擎中的解释器属于Java虚拟机底层源码的部分且程序员无法对其进行修改,所以了解即可。
字节码文件的组成
正确打开class文件
字节码文件中保存了源代码比那以后的内容,以二进制的方式存储,无法用记事本打开阅读。强行打开里面都是乱码,无法正常阅读。
- 这里推荐 jclasslib 工具查看字节码文件。
- Github地址:https://gitHub.com/ingokegel/jclasslib
class文件基本就包含这5项内容
- 基础信息:魔数、字节码文件对应的Java版本还访问标识(public final等)父类和接口
- 常量池:保存了字符串常量、类或接口名、字段名主要在字节码指令中使用
- 字段:当前类或接口声明的字段信息
- 方法:当前类或接口声明的方法信息字节码指令
- 属性:类的属性,比如源码的文件名,内部类的列表等
字节码文件组成部分-Magic魔数
上面是两个字节澳门文件用记事本打开的显示出的内容,可以看出两个开头的内容相同,相同的内容就是魔数。
- 文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容
- 软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错
- Java字节码文件中,讲文件头称为magic魔数。
字节码文件的组成部分-主副版本号
- 主副版本号指的是编译字节码文件的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2是46之后每升级一个大版本就加1;副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号。
- 版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容。
1.2之后大版本号计算方法就是:主版本号 - 44 比如主版本号52就是JDK8。
案例:主版本号不兼容导致的错误
它说类文件有错误的版本52.0(即类文件的版本号为52.0-JDK8),而应为50.0(即文件的运行环境版本号是50.0-JDK6)。无法访问 org.apache,commons.lang3.RandomstrineUtils说明是依赖需要的版本号是JDK8,而项目的环境是JDK6.
两种解决办法:
- 升级项目JDK版本(不推荐,容易引发其他的兼容性问题,并且需要大量的测试)
- 将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求(建议采用)
字节码文件的组成部分-常量池
- 常量池中的数据都有一个便不好,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到相应的数据。
- 字节码指令中通过编号引用常量池的过程叫做符号引用。
如果在Java中定义了两个及以上的相同值的参数,其在常量池中只会开辟一个空间专门存放这个值
字段下面会存放字段的名字和值在常量池中的位置编号,如果是值如字符串类型的值会指向字符串类型,然后再指向字面量的值。字段名则会直接指向字面量的值。
字节码文件的组成-方法
说方法就要引入两个额外的东西。
- 操作数栈:操作数栈是JVM对数据进行处理操作是临时存储数据的地方
- 局部变量表数组:局部变量表数组是局部变量的存放位置,是一个数组,0索引处是在main方法处引入的args,从1开始就是我们自己定义的局部变量按顺序存放。
public static void main(String[] args) {int i = 0;int j = i + 1;//下面是上面代码的字节码方法
// 0 iconst_0 将0压入操作数栈顶(iconst_数字:把数字压入操作数栈顶)
// 1 istore_1 将操作数栈顶的变量的值保存到局部变量表数组索引为1的位置
// 2 iload_1 把局部变量表数组索引为1的位置的值复制到操作数栈顶
// 3 iconst_1 将1压入操作数栈顶
// 4 iadd 将操作数栈顶两个值弹出,并计算它们的和,将结果压入操作数栈顶
// 5 istore_2 将操作数栈顶的变量的值保存到局部变量表数组索引为2的位置
// 6 return 最后返回}
}
如果在面试时,面试官问你
有些人可能会回答 i = 1 也有人会回答 i = 0 ,答案是 i = 0,但为什么呢,如果没学过Java虚拟机,你可能会回答因为 i = i++ 的操作是先赋值再 +1,i已经赋值了,+1是对数值进行+1,已经不会对 i 产生影响了,但这绝对不是面试官想要的答案。
这时我们需要从JVM的角度来回答,根据字节码指令来分析。
public static void main(String[] args) {int i = 0;i = i++;//字节码指令如下:
// 0 iconst_0 将0压入操作数栈顶
// 1 istore_1 将栈顶的0保存到数组索引1中
// 2 iload_1 将数组索引1中的值压入栈顶
// 3 iinc 1 by 1 将数组索引1中的值加1(注意:iinc i by j 是直接在数组中进行操作的,是把数组中索引为i处的值加j)
// 6 istore_1 将栈顶的0保存到数组索引1中
// 7 return 返回}
}
由上可知,在JVM中可不是先赋值再加1的,只不过是因为++操作直接在数组中进行导致后面赋值把++的操作给覆盖掉了。下面我们也看一下++i 的字节码命令
public static void main(String[] args) {int i = 0;i = ++i;//字节码指令如下:
// 0 iconst_0 将0压入操作数栈顶
// 1 istore_1 将栈顶的变量的值保存到数组1中
// 2 iinc 1 by 1 将数组1的值加1
// 5 iload_1 将数组1的值的值压入栈顶
// 6 istore_1 将栈顶的变量的值保存到数组1中
// 7 return}
字节码文件常用工具的使用
javap -v命令
- javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件里的内容。
- 直接输入javap查看所有参数
- 输入 javap -v字 节码文件名称 查看具体的字节码信息,如果需要发送给其他人可以直接在最后面加上 > 文件地址 来直接生成文件。(如果是jar包需要先使用jar -xvf命令解压)
jclasslib插件
在idea里下载jclasslib的插件
Arthas
- Arthas 是一款线上监控诊断产品,通过全局视角实时査看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
- 官网:https://arthas.aliyun.com/doc/ 与arthas相关的所有指令都可以在官网中查看。
- 下载地址:Releases · alibaba/arthas
下载下来之后解压,然后在解压后的地址处输入cmd调出命令提示符,然后输入指令java -jar arthas-boot.jar
输入指令之后你能看到你启动的程序,它会自动为你的启动类带上包名,然后你输入最前面方括号里面的数字如1就能进入你选择的程序内进行操作。
dump命令,dump可以把类的字节码文件保存到你指定的目录里面去
dump -d 目录 类的权限名(即包名.类名)
这里的路径中间的斜杠要写 / 否则会生成到默认路径下。
jad命令,jad命令能把你的字节码文件发编译成代码。
jad 类的权限名