Java SE Specifications (oracle.com)(虚拟机官网)
一、什么是JVM(初识JVM)
jvm本质是一个运行在计算机上的程序,他的职责是运行Java字节码文件。
JVM的功能
1、解释和运行
Java源代码文件编译成字节码文件,而Java虚拟机就是将字节码指令解释成机器码,最后机器码交给计算机运行。
2、内存管理
- 自动为对象、方法等分配内存
- 自动的垃圾回收机制,回收不再使用的对象
3、即时编译(提升Java性能最核心手段)
- 对热点代码进行优化,提升执行效率
Java需要实时解释,主要是为了支撑跨平台特性。
虚拟机发现一段代码字节码指令被多次调用(即热点代码),就会认为这段代码要优化,主动将这段代码解释并优化成机器码,然后将这段机器码保存在内存中,再次执行时就直接调用保存的机器码。
常见的JVM
- HotSpot(C++语言编写)虚拟机使用最广泛且最稳定。Oracle版和Open版功能上基本相同,但在垃圾回收上会有些许区别
HotSpot发展历程
二、字节码文件详解
JVM虚拟机的组成
从一个字节码文件(可来自磁盘的读取或网络传输过来)开始,JVM通过类加载器(ClassLoader)将字节码文件加载到内存中,Java虚拟机就要准备一块内存空间来存放这些字节码文件中的类、接口和对象,存放类和对象的区域就叫运行时数据区(JVM管理的内存)。
虚拟机要使用执行引擎来执行代码,将字节码文件中的指令解释成机器码,同时使用即时编译器优化性能。由于HotSpot由C++语言编写不存在字节码文件中,就要调用本地接口(调用底层虚拟机实现,由C++编写的方法如native修饰的方法),执行引擎负责调用本地接口。
字节码文件的组成(概览)
字节码文件的组成——应用场景
1、解决面试难题
若从Java语法层面解答,就浮于表面啦~~ ╮(╯▽╰)╭。若是从字节码文件中的字节码指令来回答会让人觉得你对Java虚拟机有一定的理解👍。
2、解决工作中的实际问题——版本冲突
如果掌握字节码文件组成,就很容易解决
3、解决工作中的实际问题——系统升级
字节码文件打开方式
1、jclasslib
字节码文件保存了源代码编译后的内容,以二进制的方式存储,无法直接用记事本打开阅读。推荐使用jclasslib(专业的字节码文件查看器)。
直接在idea安装jclasslib Bytecode Viewer插件即可
安装完重启idea,在view(视图中)看到Show Bytecode With Jclasslib就安装成功啦~~。😀
要想查看某个类的字节码文件要先打开这个类,然后点击Show Bytecode With Jclasslib才能查看到对应的字节码文件。
需要注意的是源代码发生变化要重新编译。
2、阿里arthas
Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
先下载安装arthas(去看arthas API文档叭~)
在下载的目录下打开命令行输入java -jar arthas-boot.jar命令查看arthas版本和运行的Java进程
输入编号5,进入程序内部查看基本信息(内存,字节码文件,CPU负载等等)。
arthas功能
arthas命令
字节码文件组成
1、基础信息:包含魔数(没打错字)、字节码文件对应的Java版本号访问标识(public final等等)、父类和接口。基础信息分成两部分一个是一般信息,另一个是接口。
2、常量池:保存了字符串常量、类或接口名、字段名,主要在字节码指令中使用。
3、字段:将源代码中的字段信息保存到了字节码文件中(即当前类或接口的字段信息)。
4、方法:将源代码中具体内容转换成字节码指令。
5、属性:类的属性,如源码的文件名、内部类的列表等。
详解字节码文件
1、基本信息
Magic魔数:隐含的,在jclasslib中看不到,每一个字节码文件都会携带魔数。
- 文件时无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。
- 软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。
只修改了扩展名,原始文件不会变化,如.png(图片)改成了.avi用播放器打开会出错,因为文件头不同,播放器无法识别。
在Java文件中这个文件头就称为magic魔数,Java字节码文件前四个字节必须是CAFEBABE,Java虚拟机在加载字节码文件过程中发现前几个字节不满足CAFEBABE要求,就会立即报错,这样就保证了文件的安全性。
主次版本号是指编译字节码文件的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用45.0-45.3,JDK1.2是46之后没升级一个大版本号就加1;次版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号。
版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容
主版本号不兼容导致的错误
由于字节码文件是52(即52-44=JDK8),而我的运行时环境是50(即50-44=JDK6),导致低运行环境去运行高字节码文件就报错啦~
解决的两种方案
- 升级JDK版本(容易引发其他的兼容性问题,并且需要大量的测试)。
- 将第三方依赖的版本号降低或更换依赖,以满足JDK版本的要求(建议这种解决方案)。
2、常量池
字节码文件中常量池的作用:避免相同内容重复定义,节省空间。
在常量池中8号编号
但是为什么要这么绕呢?
a1引用了常量池8中的内容,然后通过10去引用到字符串字面量abc
对于变量为abc,它的名字直接引用了常量池10中的abc(就是为了减少存储空间)
常量池中的数据都有一个编号,编号从1开始。在字段或字节码指令中通过编号可以快速找到对应的数据。
字节码指令中通过编号引用到常量池的过程称为符号引用。
3、方法
操作数栈是临时存放数据的地方,局部变量表(是个数组)是存放方法中的局部变量的位置。
iconst_0:将常量0放入操作数栈中。
istore_1:会将操作数栈的内容弹出来,放入局部变量表1的位置。
iload_1:将局部变量表1中的数据放入操作数栈中去。(是将数据复制到操作数栈中,局部变量1数据没有消失)。
iadd:将操作数栈顶部的两个数据进行累加,结果放入栈中。
return:方法结束,返回。
那0存放什么?
显然存放的是main方法的参数args。
i++和++i结果不同就是iinc 1 by 1 和iload_1的顺序不同造成的。
字节码常用工具:javap -v命令
javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件内容。
javap -v 文件路径(.class文件路径)> 输出位置(将字节码文件放入这个文件中)
查看字节码文件
- 本地文件可以使用jclasslib工具查看,开发环境用jclasslib插件。
- 服务器上文件用javap命令直接查看,也可用arthas的dump命令导出字节码文件到本地再查看。还可用jad命令来反编译出源码。