一、初始JVM
源代码文件.java,通过javac编译,转化为.class文件,运行这个字节码文件,
JVM本质上是一个运行在计算机上的程序,职责是将字节码文件里的字节码指令,转化为机器码,然后机器码就可以交给计算机运营。
JVM 功能一 ,解释和运行,对字节码文件中的指令,实时的解释成机器码,让计算机执行。
功能二,内存管理,自动为对象、方法等分配内存;自动的垃圾回收机制,回收不再使用的对象。对比c语言方便多了,对于对象的回收,需要程序员手动写代码进行回收。
功能三,即时编译,对热点代码进行优化,
java 如果不做任何优化,性能不如C、 C++等语言
java 语言 将字节码指令 通过java虚拟机实时的,解释成机器码,交给计算机。但是支持跨平台。
C/C++ 语言,可以将源码文件直接转化为机器码,然后交给计算机直接运行,节省了一个解释的过程。不具备跨平台特性。
java为什么要这么做呢,是为了跨平台,同一份字节码指令,通过不同平台的java虚拟机解释为不同的,机器码,例如windows的机器码,Linux的机器码,这样就实现了跨平台。
由于JVM需要实时解释虚拟机指令,不做任何性能优化不如直接运行机器码的C、C++语言。
即时编译,字节码指令,转化为机器码后,计算机可以执行,虚拟机发现这段代码被多次执行,被认定为是热点代码,有必要进行优化,所以把转化为的机器码存储到内存中,当第二次这段代码被执行的时候,直接从内存中取出机器码,进行执行,这样就节省了解释的步骤,提高了性能。
java虚拟机如何判断这个是热点代码。
Java 虚拟机判定热点代码的方式有两种:
基于采样的热点判定
主要是虚拟机会周期性的检查各个线程的栈顶,若某个或某些方法经常出现在栈顶,那这个方法就是"热点方法"。这种判定方式的优点是实现简单;缺点是很难精确一个方法的热度,容易受到线程阻塞或外界因素的影响。
基于计数器的热点判定
主要就是虚拟机给每一个方法甚至代码块建立了一个计数器,统计方法的执行次数,超过一定的阀值则标记为此方法为热点方法。
Hotspot 虚拟机使用的基于计数器的热点探测方法。它使用了两类计数器:方法调用计数器和回边计数器,当到达一定的阀值是就会触发 JIT 编译。
方法调用计数器:在 client 模式下的阀值是 1500 次,Server 是 10000 次,可以通过虚拟机参数: -XX:CompileThreshold=N 对其进行设置。但是 JVM 还存在热度衰减,时间段内调用方法的次数较少,计数器就减小。
回边计数器:主要统计的是方法中循环体代码执行的次数。
常见的JVM:《Java虚拟机规范》,由Oracle制定,内容主要包含Java虚拟机在设计和实现时需要遵守的规范,主要包含class字节码文件的定义、类和接口的加载和初始化、指令集等内容。
《Java虚拟机规范》是对虚拟机设计的要求,而不是对Java设计的要求,也就是说虚拟机可以运行在其他的语言比如Groovy、Scala生成的class字节码文件之上。
官网地址:https://docs.oracle.com/javase/specs/index.html
总结:
1、JVM到底是什么?
JVM全程Java Virtual Machine,是一个运行在计算机上的程序,他的职责是运行Java字节码文件。
2、JVM的三大核心功能是什么?
内存的管理(也就是对象的创建和回收)、解释字节码指令为机器码,即时编译(优化性能)三大功能。
3、常见的JVM虚拟机有哪些?
常见的JVM有HotSpot、GraalVM、OpenJ9等,另外DrangonWell龙井JDK也提供了一款功能增强的JVM,其中使用最广泛的还是HotSpot虚拟机。
二、字节码文件详解
1、Java虚拟机的组成
2、字节码文件的组成
3、类的声明周期
4、类加载器
1、JVM的组成
字节码文件从磁盘读取,从网络中传输过来,Java虚拟机需要把这个文件加载到内存中,后续使用起来比较高效,这个过程就是通过类加载器ClassLoader,加载class字节码文件中的内容到内存中。所以Java虚拟机需要准备一块内存空间,要存放字节码中的类和接口。
存放类、对象内存区域,叫运行时数据区域,本质就是Java虚拟机所管理的内存。
字节码被加载到内存之后,执行引擎就要执行代码,这个时候就将字节码指令解释成为机器码,并且为了性能优化,采用了即时编译器,有些对象不在使用了,也会使用垃圾回收器,回收对象。
执行引擎会负责本地接口调用,同时本地接口也会创建对象。
类加载器
运行时数据区域
垃圾回收器,【即时编译器在原理篇】。解释器和本地接口属于虚拟机底层调用。
2、字节码文件的组成
字节码文件的组成-应用场景
int i = 0;
i = i++;
最终i的值是多少
Java的反射是如何实现的【原理篇】
2.1打开字节码文件
字节码文件中保存了源代码编译之后的内容,以二级制的方式存储,无法直接用记事本打开,使用NotePad++使用十六进制插件查看class文件。
左边是当前文件的地址,中间是整个十六进制的数据,右侧是编码之后的结果。
使用jclasslib工具查看字节码文件,
https://github.com/ingokegel/jclasslib
https://github.com/ingokegel/jclasslib/releases/tag/6.0.5
2.2字节码文件的组成
主要包含五部分:
基础信息:魔数、字节码文件对应的Java版本号、访问标识(public final等等)、父类和接口。
常量池:保存了字符串常量、类或接口名、字段名,主要在字节码指令中使用。
字段:当前类或接口声明的字段信息
方法:当前类或接口声明的方法信息字节码指令,也就是方法通过编译转成字节码指令
属性:类的属性,比如源码的文件名内部类的列表等。
字节码文件的组成
重点介绍基本信息、常量池、方法,对于字段和属性不那么重要。
基本信息
字节码文件的组成部分-Magic魔数
文件是无法通过扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。
软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。
Java字节码文件,文件头是CAFEBABE,将这个文件头称为magic魔数。虚拟机加载这个文件,如果发现文件头不是几个字母,就会报错,这样就会保证加载的文件确实是字节码文件。
主副版本号
主副版本号指的是编译字节码文件的JDK版本号,主版本号用来标识大版本号,JDK1.2是46之后每升级一个大版本就加1,52-46=6,1.2+0.6=1.8,当前字节码文件的jdk版本号是1.8。副版本号是当主版本号相同时,区分不同版本的标识,一般只需要关心主版本号。版本号的主要作用是判断当前字节码文件的版本和运行的JDK版本是否兼容,例如编译字节码文件的版本是15,运行在jdk1.7,就无法运行。
举例,主版本号不兼容导致的错误
类文件错误的版本52.0,应为50
代码:
解决方案:
1、升级jdk,
2、将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求。建议这种
小结:
常量池
方法
常量池
字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。
常量池中的数据都有一个编号,编号从1开始。在字段或者字节码指令中通过编号可以快速找到对应的数据。
字节码的指令中通过编号引用到常量池的过程称之为符号引用
图片一
方法
就是当前类或者接口声明的信息,存放到字节码指令
int i =0
i = i ++
字节码指令
i++ 底层的字节码文件是,先把i=0 放到交换栈中,然后运行字节码指令 by,进行自加,最后再赋值给i,这个时候是把交换栈中的0 赋值给i,所以i=0。
对于++i 字节码文件是先在变量里进行自加,然后把i=1放到交换栈中,然后再把i=1赋值给i,所以i=1。
2.3字节码常用工具
使用idea里的插件jclasslib
使用arthas定位线上出现的字节码问题