-------------------------------------------------------------------------------------------------------------------------------------------
一个java代码文件,要想被执行,主要经过的步骤有:
源代码(SourceCode)-》编译器(预处理器preprocessor->编译器compiler->汇编程序assembler->目标代码object code->
链接器Linker->) -》可执行程序-》加载-》分配内存-》执行程序
按照JVM工作流程划分其主要内容:
编译和执行过程
类加载机制
内存分配机制
内存回收机制
-----------------------------------------------------------------------------------------------------------------------------------------------
我们知道,计算机实体机一般分为如下几个部分:指令集、计算单元、寻址方式、寄存器定义、存储单元。
其中与代码执行最密切就是指令集(CPU用来计算和控制计算机系统的一套指令集合,每一个CPU都设计规定了一系列与其他硬件电路配合的一套指令系统;通常有精简指令集RISC和复杂指令集CISC);指令集是能够直接被机器识别的机器码,但是我们人不能识别,因为汇编语言在顺序和逻辑上和机器指令一一对应,所以我们人可以通过汇编语言来助记机器指令,但并非所有的机器指令都有对应的机器指令。
而操作系统作为管理计算机的入口,所以,程序能否被执行,首先要看操作系统是否支持某种芯片的指令集。
再看JVM的组成部分:
一个抽象规范:约束定义了JVM的组成部分;(接口)
一个具体实现:不同厂商按不同规范结合不同的软件硬件在不同平台上的具体实现。(实现类)
一个运行实例:一个运行中的Java程序就是一个JVM实例。
一套JVM字节码指令集:要符合class文件字节码规范。
JVM物理结构由4部分组成:
类加载器:在JVM启动时或者类运行时加载Class文件
执行引擎:负责执行class文件中的字节码指令(相当于计算机中的CPU)。
执行引擎作用就是解析JVM字节码指令得到执行结果,但是,并不关心具体的处理方式,因为不用商家可以用不同的策略方法实现执行引擎;字节码对应了方法中的代码穿起来的流程,所以本质上讲,执行引擎就是执行方法串联起来的流程,一个流程对应了一个线程,那么,也可以说,一个线程就是一个执行引擎的实例。
内存区:划分区域以便进行存储、记录和调用模块。
一个JVM内存区会有方法区、Java堆、Java栈(虚拟机栈)、PC集群器(程序计数器)和本地方法区。
方法区和Java堆是所有线程共享(所有执行引擎可访问);
java栈和PC寄存器是线程私有,每个执行引擎启动时都会创建自己的java栈和PC寄存器,java栈用于存储方法参数、局部变量、方法返回值和运算中间结果,PC寄存器则用于记录下一条字节码指令地址。
本地方法区用于存储本地方法调用。
本地方法调用:(本地方法接口)可调用本地方代码返回执行结果。
总结其关系为一句话就是:执行引擎利用内存区和本地方法调用来执行ClassLoader类加载器加载进来的class文件的字节码。
JVM工作机制就是执行引擎的工作过程--执行引擎
1、最简单的:一次性解释字节码。
2、快,但消耗内存的:“即时编译器”,第一次被执行的字节码会被编译成机器代码,放入缓存,以后调用可以重用。
3、自适应优化器,虚拟机开始的时候会解释字节码,但是会监视运行中程序的活动,并记录下使用最频繁的代码段。程序运行的时候,虚拟机只把使用最频繁的代码编译成本地代码,其他的代码由于使用的并不频繁,继续保留为字节码--由虚拟机继续解释他们。一般可以使java虚拟机80%~90%的时间里执行被优化过的本地代码,只需要编译10%~20%对性能优影响的代码。
4、由硬件芯片组成,他用本地方法执行java字节码,这种执行引擎实际上是内嵌在芯片里的。
JVM为何采用基于栈的结构设计
基于栈的方式:所有的操作数必须先入栈,然后根据指令集中的操作码从栈顶弹出若干个元素后再将结果压入栈中。操作数入栈可以是直接常量入栈或本地变量集中的变量压入栈。
JVM是基于栈的虚拟机,为每个新创建的线程都分配一个栈,也就是说一个Java程序来说,它的运行就是通过对栈的操作来完成的。栈以帧为单位保存线程的状态。JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作。
某个线程正在执行的方法称为此线程的当前方法,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据。这个帧在这里和编译原理中的活动纪录的概念是差不多的。
从Java的这种分配机制来看,可以这样理解:栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
嵌套方法的出栈和入栈示意图:
上图中描述了嵌套方法时,stack的内存分配图,由上面可以知道,当嵌套方法调用时,嵌套越深,stack的内存就越晚才能释放,因此,在实际开发过程中,不推荐大家使用递归来进行方法的调用,递归很容易导致stack flow。
非嵌套方法的出栈入栈过程
采用基于栈的结构设计原因:
1)JVM要保证设计成的与平台无关。屏蔽平台的差异性,就要求保证在没有或者很少寄存器的机器上同样能够正确的执行Java代码。
2)为了指令的紧凑性。为了让编译后的class文件更加紧凑,降低其大小,提高字节码在网络上传输的效率。
3)及时释放内存,提高内存的利用率。