一、Java虚拟机是什么?

虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。


二、Java虚拟机的简单描述

Java虚拟机有自己完善的硬件架构,如处理器、堆栈等,还具有相应的指令系统。

Java虚拟机只识别class文件,只要是class文件都可以在Java虚拟机中运行起来,Java虚拟机还有一个特点就是跨平台,它还有不同操作系统版本的Java虚拟机,能让我们实现一次编写到处运行的理想,摆脱了硬件的束缚

大家看这个图便可以简单理解了
什么是Java虚拟以及它内部结构(JVM学习:一)
从这个图中我们可以看出
我们的Java源代码编译后的字节码文件(.class)文件运行在Java虚拟机中,而不是运行在操作系统上,这是因为操作系统识别不了字节码文件,我们需要通过Java虚拟机把它翻译成操作系统支持机器指令,也就是说Java虚拟机充当一个翻译官的职位把我们java源文件编译后的字节码文件通过类加载器加载到Java虚拟机中然后翻译成计算机可以执行的的机器指令


三、Java虚拟机的特点

Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。


四、Java虚拟机存在哪里?

很多人可能都可以不知道Java虚拟机存在哪里,好像我们写项目也没有用到Java虚拟机,其实Java虚拟机就在我们的JDK中的JRE里面

概述一下JDK和JRE的作用

  • JDK(Java Development Kit)是支持Java程序开发的最小环境
  • JRE(Java Runtime Environment)是支持Java程序运行的标准环境

什么是Java虚拟以及它内部结构(JVM学习:一)


五、Java虚拟机的内部结构

Java虚拟机主要分为五大模块:类装载器子系统运行时数据区执行引擎本地方法接口垃圾收集模块。其中垃圾收集模块在Java虚拟机规范中并没有要求Java虚拟机垃圾收集,但是在没有发明无限的内存之前,大多数JVM实现都是有垃圾收集的。而运行时数据区都会以某种形式存在于每一个JAVA虚拟机实例中,但是Java虚拟机规范对它的描述却是相当抽象。这些运行时数据结构上的细节,大多数都由具体实现的设计者决定。
什么是Java虚拟以及它内部结构(JVM学习:一)

类加载子系统(Class Load SubSystem)

1.类加载子系统的作用

  • 类加载器子系统负责从文件系统或者网络中加载 Class 文件,Class 文件在文件开关有特定的文件标识
  • ClassLoader 只负责 Class 文件的加载,至于它是否可以运行,则由 Execution Engine(执行引擎)决定
  • 加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是 Class 文件中常量池部分的内存映射)

2. 类加载子系统加载的工作流程

什么是Java虚拟以及它内部结构(JVM学习:一)

  1. 加载

类由此组件加载。启动类加载器 (BootStrap class Loader)、扩展类加载器(Extension class Loader)和应用程序类加载器(Application class Loader) 这三种类加载器帮助完成类的加载。

  • 启动类加载器 – 负责从启动类路径中加载类,无非就是rt.jar。这个加载器会被赋予最高优先级。

  • 扩展类加载器 – 负责加载ext 目录(jre\lib)内的类.

  • 应用程序类加载器 – 负责加载应用程序级别类路径,涉及到路径的环境变量等etc.

上述的类加载器会遵循委托层次算法(Delegation Hierarchy Algorithm)加载类文件。

  1. 链接
  • 第一步 校验 – 字节码校验器会校验生成的字节码是否正确,如果校验失败,我们会得到校验错误。

  • 第二步 准备 – 分配内存并初始化默认值给所有的静态变量。

  • 第三步 解析 – 所有符号内存引用被方法区(Method Area)的原始引用所替代。

3 初始化

  • 这是类加载的最后阶段,这里所有的静态变量会被赋初始值, 并且静态块将被执行。

运行时数据区(Runtime Data Area)

Java虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程启动和结束而建立和销毁。包括以下几个区域,如下图所示
什么是Java虚拟以及它内部结构(JVM学习:一)

1、线程共享数据区

1.1 堆区(Heap Area)

在Java中我们最熟悉的就是对象,在内存中用来存放内存对象实例的区域称之为堆(Heap),此区域由线程内存共享,在进行垃圾回收时,此区域是垃圾回收器重点关注的地方,因此我们也称之“GC堆”。早起的Java虚拟机严格按照JVM虚拟规范来涉及:任何的对象实例及数组都要在堆上分配,但随着JIT编译器的发展,许多新生的优化技术(比如对象逃逸分析技术)可允许对象根据实际情况在栈中分配,也就是说现在的虚拟机中的对象并不一定是分配在Heap当中。 要注意堆并不一定是连续的内存空间,只要保证是逻辑上的连续就行。(那么对象在连续的物理内存空间上分配和在不连续的物理内存空间分配的区别是什么?)

可能会抛出OutOfMemoryError:当没有足够的区域实现对象实例的分配,并且该堆也没法实现扩展。


1.2 方法区(Method Area)

和堆相对的是方法区,用来存储已经被虚拟机加载的类信息、常量、静态变量以及经过JIT优化过后的代码等,和堆一样的是此区域也是线程共享的内存区域。 在虚拟机规范中,方法区虽然被划分为堆的一个逻辑部分,本质上并不属于堆,因此也称为非堆(Non-Heap)。 对于方法区的实现,虚拟机规范限制较少,因为不同的JVM团队可以选择不同的实现方案,比如HotSpot会用永生代来实现,而JRockit则采用了另外的方案。除此之外,方法区在物理上的内存空间可连续也可不连续、可实现动态扩展该区域的大小以及可实现或者不实现垃圾回收(对该区域的回收通常是常量池及类的卸载,但是效果往往较差,因此对该区域很少实现垃圾回收)。

可能会抛出OutOfMemoryError:当方法区无法满足内存分配需求时


1.3 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放编译期间生成符号引用和各种字面量,其中符号引用会在连接中的解析阶段直接被替换为直接引用。需要注意的是,常量也可以在运行期间动态的被添加到常量池,以便提高效率,比如常见String中的intern()方法。

可能会抛出OutOfMemeoryError:因常量池也属于方法区,因此在申请不到内存的时候也会抛出异常。


2、线程私有数据区

2.1 虚拟机栈
当一个线程被创建时,虚拟机栈也随之被创建,该区域为线程私有,无法被其他线程使用,因此不存在线程安全问题(从一个线程的角度来看,其中的方法执行顺序都是串行;当线程销毁时,该虚拟机栈也随之被销毁,JVM会自动释放该内存区域。虚拟机栈本身是一个先进后出的数据结构。在该线程中,每执行一个方法,就会生成一个栈(Stack Frame),并被压入虚拟机栈,当该方法执行完毕之后,该栈帧会被弹出,也就是说方法调用和方法返回的过程对应着栈帧入站和出栈的操作。

可能抛出的异常:

StackOverflowError:如果线程内请求的栈深度超过虚拟机允许的深度,会抛出该异常。 OutOfMemoryError:如果虚拟机允许动态扩展栈的大小,但是在扩展时无法申请到足够的内存空间,会抛出该异常。


2.2 本地方法栈

虚拟机栈中栈帧对应的是java方法,执行的是字节码,而本地方法栈则对应的是Native方法。另外需要本地方法的实现方式并没有严格的规定,其使用的语言、数据结构没有统一的规定,对于该部分的实现,不同的团队也会有不同的实现。

可能抛出的异常: 和虚拟机栈类似,也会抛出StackOverflowError和OutOfMemoryError异常。


2.3 程序计数器

每个线程内都有一块内存区域用来记录要执行的指令的地址,也就是所说的程序计数器。它用来描述需要执行哪一条字节码指令,该区域同样为线程私有。在一个线程内,从宏观的角度去看,所有要执行的指令可视为一个指令表(如下图所示),而程序计数器可视为当前正在执行的指令的行号。在执行过程中,字节码解释器,通过修改该区域的值来选择下一条要执行的指令的地址,而执行引擎则根据该区域的值来执行相应的指令,进而实现跳转、循环、线程恢复等操作。如果执行的native方法,则该计数器的值为Undefined。
什么是Java虚拟以及它内部结构(JVM学习:一)

(上面两图我表示一个函数的指令集合,因为在线程内,函数的执行时串行的,同样可视为一个大的指令表)

现在我们简单的谈谈程序技术器为什么会被设计成线程私有的?如果JVM是单线程,那么程序计数器被设计成全局性的,是没有问题,因为同一时刻只能有一条指令被执行。而JVM是多线程,并且其多线程是则主要是通过处理器切换时间片来实现的。在支持多核多线程的处理器中,在同一时刻会有多个线程同时执行,而每个线程执行的指令或许都是不同的,如果此时在将程序计数器设计成全局唯一的,那显然是有问题的(程序计数器在某一个时刻,只能指向一条正在执行的指令),因此为了在线程切换能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,且不能被外部随意修改和访问。

该区域占用空间极小,虚拟机规范中没有规定该区域会抛出OutOfMemoryError异常,换言之,根本不会抛出异常。

对象的内存结构、创建以及对象查找

  1. 对象的内存结构

上面简单谈了JAVA的内存结构,下面简单的说说我们常见的“对象”的内存结构。我们以Hotspot虚拟机为例,其对象的内存结构大体分为三块区域:
1、对象头;
2、实例数据;
3、对齐填充。
结构如下图所示:
什么是Java虚拟以及它内部结构(JVM学习:一)

1、实例数据是JAVA开发者最熟悉的部分,存储了我们在程序代码中所定义的各种类型的字段内容,是我们在开发过程中终点关注也是频繁操作的地方。 2、对齐填充区根据JVM的设计,可有可无,没有具体的功能实现,更多是为了让整个对象整齐(以倍数的形式存在),能被以统一的形式被处理。 3、对象头主要是对内服务,普通JAVA无需关心,其目的是为该对象设置附加数据,用于内容的识别和查找。该区域被划分为两部分,一部分用于存储对象自身的运行时数据,比如hashcode,锁标志,时间戳等信息,该部分也被称为Mark Word。另一部分则是类型指针1,通过该指针,JVM能够确定该对象是那个类的实例。(为了更方便认知,我将第一部分称之为状态区,第二部分成为定位区)

那么对象头的存在解决了什么问题呢?通俗的说就是帮助JVM虚拟机了解这个对象:解决了它是谁的实例,它的独特性质是什么? 现在再来细化一下上面的结构图:

什么是Java虚拟以及它内部结构(JVM学习:一)

  1. 对象的创建过程

在JAVA开发中,常用的几种创建对象的方式有以下几种:

通过new创建对象

通过使用反射机制,java.lang.reflect.Constructor类的newInstance()

通过调用对象的clone()方法

同对象反序列化技术,调用java.io.ObjectInputStream对象的readObject()方法。 第一种方式是我们最常使用,我们就来简单的解释一下该过程中,对象是如何被创建起来的,首先我们简单的参考该流程图:
什么是Java虚拟以及它内部结构(JVM学习:一)

1、虚拟机碰到new指令时,首先检查该指令的参数是否在常量池中能定位到一个类的引用,并检查符合引用代表的类是否已经被加载、解析和初始化过,如果没有则需要首先执行类加载过程。 2、类加载检查通过后,则为该对象分配内存空间。 3、内存分配完成后,该内存区域会被重置,业绩空间会被初始化为零值(对象头例外) 4、设置对象头信息 5、前四部完成之后,代表已经生成了一个未被初始化的对象,但是init()方法还未执行,对象的字段还是零。因此一旦init()执行完毕之后,一个完整的对象才正式生成。

  1. 对象的查找

在栈中,对象的引用(reference)并不代表堆中真正的对象,那如何通过栈中对象的引用(reference)来操作堆中的具体对象呢?这个从对象引用来定位到真实对象的过程有两种实现的方式: 1、句柄定位 该种方式会在堆中实现划分出一块区域做句柄池,reference中存储的是对象的句柄地址,而句柄中包含对象的实例数据和类型数据的具体内存地址。 其结构如下:
什么是Java虚拟以及它内部结构(JVM学习:一)

2、直接定位 在该种方式中,reference中存储的是堆中真实对象的地址。

两者比较:句柄定位中reference存储的是句柄地址,相对稳定,对象改变时不会影响只需修改对象实例数据,而无须修改reference。 和句柄定位相比,直接定位的reference存储的是就是对象的地址,少了一层定位的过程,因此效率更高。


下期我们了解Execution Engine和GC机制
什么是Java虚拟以及它内部结构(JVM学习:一)
部分来源于::Java高级架构师微信公众号

相关文章:

  • 2021-10-07
  • 2021-07-05
  • 2021-08-05
  • 2021-08-27
  • 2021-10-24
  • 2021-06-07
  • 2021-12-27
  • 2021-12-19
猜你喜欢
  • 2021-10-15
  • 2021-06-06
  • 2021-08-08
  • 2021-08-29
  • 2021-12-04
  • 2022-02-16
相关资源
相似解决方案