JVM学习之路(1)初识JVM与字节码文件
目录
欢迎关注我的博客!26届java选手,一起加油💘💦👨🎓😄😂
初识JVM
JVM是java虚拟机,本质上是一个运行在计算机上的程序,他的职责是运行java字节码文件。
JVM的功能有:
- 解释和运行:对字节码文件中的指令,实时的解释成机器码,让计算机执行。
- 内存管理:自动为对象,方法等分配内存;自动的垃圾回收机制,回收不再使用的对象。
- 即时编译:对热点代码进行优化,提升执行效率。
同时,为了跨平台性,牺牲了部分性能进行实时编译:
即时编译是怎么做的?对于热点字节码文件,进行解释后放到内存,下次再调用无需解释直接调用。
使用cmd和java -version可以查,我们的HotSpot虚拟机
常见的JVM虚拟机还有HotSpot, GraaLVM,OPENj9等,另外DragonWell龙井JDK也提供了一款功能增强版的JVM。其中使用最广泛的是HotSpot虚拟机。
字节码文件详解
Java虚拟机的组成
主要是类加载器,运行时数据区域,垃圾回收器,即时编译器(后续讲解),而解释器和本地接口程序员无法修改和接触到源码,做了解即可。
字节码文件的组成
基本信息
魔数
- 文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。
- 软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。
- Java字节码文件中,将文件头称为magic魔数。
主副版本号
- 主副版本号指的是编译字节码文件的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2是46之后每升级一个大版本就加1; 副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号。
- 版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容。
1.2之后大版本号计算方法就是主版本号-44比如主版本号52就是JDK8
主版本号不兼容导致的错误
有两种解决方法:
1.升级JDK版本(容易引发其他的兼容性问题,并且需要大量的测试)。
2.将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求(建议采用)。
其他基础信息
访问标识和类、父类、接口索引。
常量池
字节码中常量池的作用:避免相同的内容重复定义,节省空间。
如下的两个字符串,实际上就是相同内容的重复定义。
线程池节省空间的原理是:有多个相同常量的时候,只需要在常量池中存放一份字符串,再被引用就可以节省空间。
以如下代码进行示范:
public class ConstantPoolTest {
public static final String a1 = "我喜欢吃蛋糕";
public static final String a2 = "我喜欢吃蛋糕";
public static void main(String[] args) {
ConstantPoolTest constantPoolTest = new ConstantPoolTest();
}
}
编译后打开jcclasslib,可以看到如下信息:
两个String常量都指向一个索引,而我们进入这个索引后,可以看到又指向了下一个索引:
继续进入,终于看到了最后的字符串字面量。
为什么要先找字符串,再找字面量?而不是直接找到字面量呢? 不能直接通过字段找到字面量吗?答案是不可以的,因为java中有一个字符串常量池,当字节码文件被解析并加载之后,需要把String类型的常量池中的内容加载到字符串的常量池中,所以必须要有一个类型为String。
- 常量池中的数据都有一个编号,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据。
- 字节码指令中通过编号引用到常量池的过程称之为符号引用。
方法
一个有意思的面试题如下:
这个面试题的答案简单,但是我们从来没深入的理解原理。
字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。
操作数栈是临时存放数据的地方,局部变量表是存放方法中的局部变量的位置:
举例:
public static void main(String[] args) {
int i = 0;
int j = i + 1;
}
可以看到我们定义的i和j都在‘名字’这一列中。
处理过程如下:
首先
int i = 0;
iconst_0 :把0放到操作数栈中
istore_1 把0赋值给1号位置,此处的1号位置就存放了i
int j = i + 1;
iload_1 :把1号位置复制到操作数栈,而不是像store那样移走,只是复制
iconst_1: 把数字1放到操作数栈中
iadd :两数相加
istore_2 : 把结果放到位置为2的地方,此处是j。
return 结束
了解完以上举例的字节码执行流程,接下来看一开始提到的题目:
i = i ++ 的执行流程:
先把0放到操作数栈,然后把0放到局部变量表数组的1号位置,
再i_load复制到操作数栈中,此时局部变量中的1号位置还有i = 0 ;
然后到 iinc 1 by 1 是在局部变量表1号位置增加1,而不用取到操作数栈 ,此时操作数栈还是0 ,而局部变量表中的 i 已经 为 1 了;
执行到 istore_1 的时候就把操作数栈中的 0 又放回到局部变量表中了, 所以 i 依然是 0;
i++和++i是load和increase 的顺序有区别导致结果不同了。先iinc 1 by 1 导致了在局部变量表已经变成1 了,取到操作数栈再存回来也依然是1 。
常用字节码工具
常用工具 javap - v
javap 是 JDK 自带的反编译工具, 可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件内容。
直接输入javap查看所有参数。
输入javap -v 字节码文件名称 查看具体的字节码信息。(如果jar包需要先使用 jar –xvf 命令解压)
常见工具 jclasslib插件
jclasslib也有Idea插件版本,建议开发时使用Idea插件版本,可以在代码编译之后实时看到字节码文件内容。
常见工具 arths
- Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
- 官网: https://arthas.aliyun.com/doc/
通过 java -jar arthas-boot.jar启动,
输入想进入的进程前方的数字就可以进入内部查看细节。
arthas 有许多的功能:
dump 类的全限定名:dump已加载类的字节码文件到特定目录。
jad 类的全限定名: 反编译已加载类的源码。
思路:
- 在出问题的服务器上部署一个 arthas,并启动。
- 连接arthas的控制台,使用jad命令加上想要查看的类名,反编译出源码。
- 确认源码是否是最新的。