三大java虚拟机_JVM虚拟机三大核心

jvm是JAVA Virtual Machine的缩写,即java虚拟机。是运行所有java程序的抽象计算机,是java语言运行的环境。

jvm的三大核心是类加载器、执行引擎、运行时数据区。

个人画了张图,但也不知道是不是有问题,但个人这么理解的,错了欢迎指出:

363facb62a59

java源文件(.java文件)经过前端编译器(java或ECJ,即Eclilpse compile for java)编译为java字节码文件(.class文件),然后JRE加载java字节码文件,载入系统分配给jvm的内存区,然后执行引擎解释或编译类文件,再由即时编译器将字节码转为机器码。

一类加载器

类加载的层次:类采用的是双亲委托模式:

363facb62a59

类加载机制示例图

类加载的过程,如图:

363facb62a59

image

1.加载:类加载器将.class文件加载至运行时数据区的方法区后,会在堆中创建一个java.lang.Class对象,用来封装类位于方法区内的数据结构。Class类的构造函数是私有的,只有JVM能够创建。因此Class对象是反射的入口,使用该对象就可以获得目标类所关联的具体的数据结构。

2.验证:验证主要有四点,文件格式验证、元数据验证、字节码验证、符号引用验证。

i.文件格式验证:主要是验证魔数,版本号等信息。魔数大家都知道吧?就是二进制读取文件时,文件起始的几个字节,表明了文件的格式类型,文件的后缀名可以改变,但是二进制中的这些魔数才算是真正确认文件格式的根本。

ii.元数据验证:语义分析,是否符合虚拟机规范

iii.字节码验证:指令级别的语义验证,确保指令不会跳转到方法体以外的代码上

iv.符号引用验证:确保符号唯一等

3.准备:正式为类静态变量分配内存并设置初始值,只有static修饰的变量,各种类型都会被赋予相应的零值

4.解析:解析阶段就是将符号引用转为直接引用。

所有的符号引用最终都会转为直接引用,也只有转为直接引用才能使用。

符号引用转为直接引用的时候可以看作是对类自身以外的信息进行匹配验证,例如通过全限命名(包名+类型)。什么是符号引用?什么是直接引用?

i.直接引用:是一个能直接指向所引用项的句柄,比如指针之类的。

ii.符号引用:是一串唯一的字符串,它给出了被引用项的名字并且可能会包含一些其他的关于这个引用项的信息,这些信息必须足以唯一的识别一个类、字段、方法。为什么会有符号引用?或者说符号引用是怎么产生的?

类在编译的时候,很多时候java类并不知道所引用的类的实际地址,只能用符号来代替,它们就被称为符号引用。

5.初始化:这是类加载机制的最后一步,在这个阶段java的程序代码才真正开始执行。在准备阶段已经为类的静态变量分配过一次内存和赋值了一次。在初始化的阶段将会根据程序员的需求赋值。例如static int a = 5; 准备阶段为这个a分配了内存,并初始化为a=0,现在会赋值a=5;这个阶段就是执行构造器方法的过程。

初始化步骤:

i.如果这个类还没有被加载和连接,程序会先加载并连接该类

ii.如果该类的直接父类还没有被初始化,程序会先初始化其直接父类

iii.如果类中有初始化语句,则系统依次执行这些初始化语句

类的初始化时机,只有对类主动使用的时候才会导致类的初始化,类的主动使用包括六种。

i.创建类的实例,也就是new的方式

ii.访问某个类或接口的静态变量,或者对该静态变量赋值

iii.调用类的静态方法

iv.反射

v.初始化某个类的子类,其父类也会被初始化

vi.java虚拟机启东市标明为启动的类,直接用java.exe民gluing运行的某个类new一个对象的过程发生了什么?

a.确认元信息是否存在。当JVM接收到new指令的时候,首先在Metaspace元空间中检查需要创建的类元信息是否存在。若不存在,那么在双亲委托模式下,使用当前类加载器以ClassLoader+包名+类名为key进行查找对应的class文件,如果没有找到会抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class对象。

b.分配对象内存。首先计算对象占用空间的大小,如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小,接着再堆中划分一块内存给新对象。在分配内存空间时,需要进行同步操作,比如CAS失败重试、区域加锁保证分配操作的原子性

c.设置默认值。成员变量值都需要设定默认值,即各种不同形式的零值

d.设置对象头。设置对象的哈希码、GC信息、锁信息、对象所属的类元信息等

e.执行init方法,初始化成员变量,执行实例化代码块,调用类的构造方法,并将堆内对象的首地址赋值给引用变量。类加载器为什么要将.class文件的二进制数据从外部存储器读入内存?

因为CPU的处理速度是远远大雨调用数据的速度的,如果不提前将数据放入内存会导致数据脱节,需要内存起到缓冲的作用。类加载器在什么时候会启动?

类加载器并不需要等到某个类“首次主动使用”时再加载它。JVM规范允许类加载器在预料到某个类要被使用时就预先加载它,如果再加载的过程中遇到.class文件缺失或者存在错误,类加载器必须在程序首次使用该类时报告错误LinkageError,如果该类一直没有被程序使用,那么加载器就不会报告错误。从什么地方记载.class文件?

在本地磁盘中、网络加载、数据库、压缩文件、其他生成文件(JSP应用)

二执行引擎

执行引擎是建立在物理机、硬件和操作系统层面之上,引擎在执行代码时会有解释执行和编译执行那两种选择。输入字节码,字节码连续输出结果。解释执行和编译执行分别是什么?

1.编译:将源代码一次性转成目标代码的过程,执行编译过程的程序叫做编译器

像静态语言,编译器一次性生成目标代码,优化更充分,程序运行时速度更快。

a.对于相同的源代码,编译所产生的目标代码执行速度更快

b.目标代码不需要编译器就可以运行,在同类操作系统使用灵活

2.解释:将源代码逐条转换成目标代码同时逐条运行的过程,执行解释过程的程序叫做解释器。

脚本语言(解释):执行时需要源代码,维护更加灵活

a.解释执行需要保留源代码,程序纠错和维护更方便

b.只要存在解释器,源代码可以在任何操作系统上运行,可以执行好

Java语言的执行是有解释执行和编译执行两种的。解释器和即时编译器能够相互协作,各自取长补短,从而提高运行效率。

三运行时数据区

运行时数据区包括方法区、java虚拟机栈、本地方法栈、垃圾回收堆、程序计数器。

1.本地方法栈:这是一个native方法栈

2.程序计数器:程序计数器的内存空间小,是线程私有的。它可以看作是当前线程所执行的字节码的行号指示器。线程主要是执行任务,而任务执行到了哪里,需要程序计数器来记录。

字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。

由于java虚拟机的多线程是通过线程轮流切换并分配处理器的执行时间来实现的,这里涉及到了时间片轮转机制。所以为了线程切换后能恢复到正确的执行位置,每一条线程都有一个独立的程序计数器,各条线程的计数器是相互不影响的,独立存储,所以它是线程私有的。

如果线程执行的是java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果是native方法,这个计数器的值为Undefined。这个内存区域时为一个在java虚拟机中没有任何规范OutOfMemory情况的区域

3.方法区:它属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。

a.静态常量池即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。用于存放编译期生成的各种字面量和符号引用,字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

·类和接口的全限定名

·字段名称和描述符

·方法名称和描述符

b.运行时常量池属于方法区的一部分。它是虚拟机在完成类加载后,将class文件中的常量池载入到内存中,并保存在方法区中。它相对于Class文件常量池的另一个重要特征是具备动态性,java语言并不要求常量一定只有编译期才能产生,也就是并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。

常量池的好处:

常量池就是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译期间就将所有的文字放入到一个常量池中。

1.节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间

2.节省运行时间:比较字符串时,==比equals()快,对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

4.垃圾回收堆:线程共享,主要存放对象实例和数组。内部可以划分出多个线程私有的分配缓冲区。可以位于物理上不连续的空间,但是逻辑上要连续。

从内存角度看,由于收集器基本都采用分代收集算法,所以对空间还可以细分:分为新生代和老年代。这里可以去看分代算法。

5.java虚拟机栈:线程私有,生命周期和线程一致。描述的是java方法执行的内存模型。

每个方法在执行的时候都会创建一个栈桢,每个方法从调用直至执行结束,就对应着一个栈桢从虚拟机栈的入栈到出栈的过程。什么是栈桢?

java虚拟机以方法作为最基本的执行单元,每个方法执行的时候都会创建一个栈桢。

栈桢分为:局部变量表、操作数栈、动态链接、方法出口、其他额外的附加信息。

a.局部变量表:是一组变量的存储空间,用于存放方法参数和方法内部定义的局部变量

b.操作数栈:这是一个后进先出栈,它的最大深度在编译的时候就已经确定。当一个方法刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入或提取内容,这就是入栈和出栈的动作

c.动态链接:每一个栈桢都包含一个执行时常量池中该栈桢所属方法的引用,持有这个引用是为了支持方法调用的过程中动态链接。这个引用是符号引用,不是方法的实际入口地址,需要动态链接找到具体的方法入口。这个特性给java带来了更加强大的动态扩展能力,但也使得java方法的调用过程变得相对复杂起来,需要在类加载期间,甚至是运行期间才能够确定目标方法的直接引用

d.方法出口:这里分为正常完成出口、异常完成出口、方法正常退出。

正常完成出口:方法正确执行,执行引擎遇到方法返回的指令,回到上层的方法调用者

异常方法出口:方法执行的过程中发生异常,并且没有异常处理,这样是不会给上层调用者产生任何返回值。

方法正常退出:将会返回程序结束时的值给上层方法,经过调整之后指向方法调用者后面的一条指令,继续执行上层方法。

e.其他额外的附加信息:《java虚拟机规范》允许虚拟机实现增加一些规范中没有的信息到栈桢中,这些信息被称为附加信息

说明:两个栈桢作为虚拟机的元素是相互独立的,但是大多数虚拟机的实现都会做一些优化处理,让两个栈桢的部分重叠。让下面的栈桢的部分操作数栈与上面栈桢的部分局部变量表重叠在一起,这样在进行方法调用的时候就可以共用一部分数据,无需进行额外的参数的复制传递。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值