什么是JVM
jvm它是一个虚构出来的机器,但是它却又是通过在实际的计算机上仿真模拟各种功能来实现的。jvm包含了一套字节码的指令集,有一组寄存器,一个栈,一个垃圾回收堆,一个存储方法域。JVM使得Java程序只需要生成在Java虚拟机上运行代码,就可以在多种平台不加什么修改地运行。JVM在执行字节码的时候,最终还是把字节码解释成机器指令执行。
JDK、JRE、JVM有什么关系
「JDK:」 也就是开发者用来编译,调试程序用的开发包,JDK也需要JAVA程序,需要在JRE上运行。
「JRE:」 Java平台,所有的Java程序都要在JRE的环境才可以运行。
「JVM:」 它是JRE的一部分,是一个虚构出来的计算器,是通过在实际的计算机来模拟计算机功能实现的。
JVM执行程序的过程
一个Java文件从编码开始到执行需要经过几个阶段:
1、编译阶段:首先.java文件经过了Javac进行编译成了.class文件。
2、加载阶段:紧接着.class文件经过了类加载器加载到JVM的内存当中。
3、解释阶段:class字节码经过了字节码解释器解析成系统可以识别到的指令码
4、执行阶段:向硬件设备发送指令码来进行操作。
「再细讲一下每一个阶段」
编译阶段
****类的编译阶段主要的目的就是把源码文件编译成为可以让JVM解析的class文件,这个阶段会经过的词法分析、语法的语义分析。
class文件包含了哪些内容呢?
「Magic Number:」 这个是在.class文件头的四个字节,作用的话就是定义识别的标准,只有符合了标准才可以被JVM解读。
「版本号:」 编译class文件的JDK版本号,这些版本是可以向下兼容的
「常量池:」 常量池里的信息主要有字面量、基本类型常量、和符号引用(类和接口全限定名,方法名和描述符等等)。
「访问标志:」 该类是不是接口、注释、枚举、模块。
「类索引:」 类的索引、父类的索引、接口的索引集合,用于来确定类的继承实现关系。
「字段表集合:」 这个是用于描述接口或者是类里声明变量的信息,如(public/private/protected)。
「方法表集合:」 方法表集合跟字段表集合类似,也就是用来保存方法的相关信息,包括了方法的名称
「属性表集合:」 这里包括了类、方法、实例变量的指令码。
加载阶段
加载这个阶段就是主要把.class文件加载到JVM内存里,这个阶段有装载、连接、初始化这三个流程
「装载:」 装载阶段呢就是把class里的信息读取到内存当中去,首先是通过了类的全限名读取到此类的二进制流,紧接着把字节流里描述静态结构的信息转化成为方法区里的运行时数据结构。在加载阶段的最后会在Java堆生成一个可以代表这个类的java.lang.class对象,作为了这个对象的访问入口。
「连接:」 这个连接阶段会进行对class的信息来进行验证,然后为类变量来分配内存空间,并且赋予默认值。首先是对class的内容来验证字节是否符合了JVM的规范,然后为静态的变量来分配内存空间,最后进行解析,把符号引用转换成为直接引用,因为这里的类信息已经在内存当中了,所以会把引用对象换成了对象在内存里的实际地址。
「初始化:」 初始化阶段主要是来执行了初始化静态块的内容,并且为静态变量进行真正的赋值。
解释阶段
解释这个阶段是在代码执行的期间触发的,当开始执行一个类的方法的时候,首先是通过这个类的对象来作为入口,来找到相对应的字节码信息,然后再通过解释器把字节码解释成指令码。在最开始的执行过程图里有两个解析器,解释器有字节解释器与即使编译器JIT,一般的情况是运行代码的时候会使用的默认字节码解释器来解析指令,只有是当某一个方法是热点方法,即使编译器就会把热点方法的指令码进行保存,等下次执行的时候就不用重复的解析了,得以优化。
执行阶段
操作系统把解释器出来的指令码,通过调用系统的硬件执行最终的程序指令。
Java内存间的交互操作
在Java的主内存与工作内存之间是如何来进行具体的交互协议的呢?就是一个变量是怎么从主内存拷贝到工作内存的呢这一类细节,在Java内存模型中有八种操作,每一种操作都是原子的,不可再分的。
1、「lock(锁定):」 锁定这种操作作用于主内存的变量,它会把一个变量标记成为一条线程独占的状态
2、「unlock(解锁):」 作用于主内存的变量,把一个处于锁定的变量释放出来,释放后的变量才可以被其他线程锁定。
3、「read(读取):」 作用于主内存的变量,把一个变量从主内存传输到线程的工作内存当中,以便随后的load使用。
4、「load(载入):」 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入到工作内存的变量副本当中。
5、「use(使用):」 作用于工作内存的变量,把工作内存当中的一个变量值传递给了执行引擎
6、「assign(赋值):」 作用于工作内存的变量,它把一个执行引擎接受到的值赋给工作内存的变量
7、「store(存储):」 作用于工作内存的变量,把工作内存当中的一个变量值传送到主内存当中,以便随后的write操作。
8、「write(写入):」 作用于主内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量当中。
上面的八种内存交互操作必须满足的规则
「第一、」 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了,但是工作内存不接受,或者是从工作内存发起了回写了,但是主内存不接受的情况出现。
「第二、」 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存当中改变了之后就必须把该变化同步回主内存。
「第三、」 不允许一个线程无原因地(没有发生任何assign操作)把数据从线程的工作内存同步回主内存。
「第四、」 一个新的变量只能够在主内存“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)变量,换一句话来说就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。
「第五、」 一个变量再次同一个时刻只允许一条线程对其进行lock操作,但是lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才回被解锁。
「第六、」 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或是assign操作初始化变量的值。
「第七、」 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
「第八、」 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。
Volatile变量的特殊规则
当变量被定义成volatile之后,保证此变量对所有线程的可见性,这里所说的可见性是指当一条线程修改了这个 变量的值,新值的话对于其他的线程来说是可以马上得知的,普通的变量不可以做到这一点,因为普通变量的值在线程里的传递时均需要通过主内存来完成。
Java内存模型里对volatile变量定义的特殊规则有:
(1)线程对变量的load、read的操作需要连续的并且一起出现的,要求是在工作内存当中,每次使用变量的时候都必须要先从主内存刷新最新的值,这也保证能看见其他线程对变量所做的修改。
(2)线程对变量store、write操作需要连续的并且是一起出现的,要求是在工作内存当中,每一次修改变量后都必须立刻的同步回主内存当中,用于保证其他线程可以看到自己对变量V所做的更改。