JVM系列(一) - JVM整体概述

前言

JVMJava Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构的计算机,是经过在实际的计算机上仿真模拟各类计算机功能来实现的。java

JVM屏蔽了与具体操做系统平台相关的信息,使Java程序只需生成在Java虚拟机上一次编译,屡次运行,具备跨平台性JVM在执行字节码时,实际上最终仍是把字节码解释成具体平台上的机器指令执行。编程

Java虚拟机包括一套字节码指令集、一组寄存器、一个、一个垃圾回收堆和一个存储方法区后端

本文将简述如下内容:数组

正文


JVM是什么

JDK、JRE和JVM对比

JVMJREJDK 都是 java 语言的支柱,他们分工协做。但不一样的是 Jdk 和 JRE是真实存在的,而 JVM 是一个抽象的概念,并不真实存在。缓存

JDK

JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。JDK 物理存在,是 programming toolsJRE 和 JVM 的一个集合。安全

JRE

JRE(Java Runtime Environment)Java 运行时环境,JRE 是物理存在的,主要由Java API 和 JVM 组成,提供了用于执行 java 应用程序最低要求的环境。网络

JVM

JVM是一种用于计算设备的规范,它是一个虚构的计算机的软件实现,简单的说,JVM是运行byte code字节码程序的一个容器。多线程

JVM的特色

  • 基于堆栈的虚拟机 :最流行的计算机体系结构,如英特尔X86架构和ARM架构上运行基于寄存器 。好比,安卓的Davilk虚拟机就是基于寄存器结构 可是,JVM是基于栈结构的。架构

  • 符号引用 :除了基本类型之外的数据 (类和接口) 都是经过符号来引用,而不是经过显式地使用内存地址来引用。框架

  • 垃圾收集 :一个类的实例是由用户程序建立和垃圾回收自动销毁

  • 网络字节顺序 :Java class文件用网络字节码顺序来进行存储,保证了小端的Intel x86架构和大端的RISC系列的架构之间的无关性。

JVM字节码

JVM使用Java字节码的方式,做为Java 用户语言 和 机器语言 之间的中间语言。实现一个通用的、 机器无关 的执行平台。

JVM能干什么

基于安全方面考虑,JVM 要求在 class 文件中使用强制性的语法和约束,但任意一门语言均可以转换为被 JVM 接受的有效的 class 文件。做为一个通用的、机器无关的执行平台,任何其余语言的实现者均可将 JVM 看成他的语言产品交付媒介。

JVM 中执行过程以下:

  • 加载代码
  • 验证代码
  • 执行代码
  • 提供运行环境

JVM生命周期

  • 启动:任何一个拥有main方法的class均可以做为JVM实例运行的起点。
  • 运行main函数为起点,程序中的其余线程均有它启动,包括daemon守护线程和non-daemon普通线程。daemonJVM本身使用的线程好比GC线程,main方法的初始线程是non-daemon

  • 消亡:全部线程终止时,JVM实例结束生命。

JVM组成架构

JAVA 代码执行过程以下:

1. 类加载器(Class Loader)

类加载器 负责加载程序中的类型(类和接口),并赋予惟一的名字予以标识。

JDK 默认提供的三种 ClassLoader以下:

类加载器的关系

  1. Bootstrap Classloader 是在Java虚拟机启动后初始化的。

  2. Bootstrap Classloader 负责加载 ExtClassLoader,而且将 ExtClassLoader的父加载器设置为 Bootstrap Classloader

  3. Bootstrap Classloader 加载完 ExtClassLoader 后,就会加载 AppClassLoader,而且将 AppClassLoader 的父加载器指定为 ExtClassLoader

类加载器的做用

Class Loader 实现 负责加载
Bootstrap Loader C++ %JAVA_HOME%/jre/lib%JAVA_HOME%/jre/classes以及-Xbootclasspath参数指定的路径以及中的类
Extension ClassLoader Java %JAVA_HOME%/jre/lib/ext,路径下的全部classes目录以及java.ext.dirs系统变量指定的路径中类库
Application ClassLoader Java Classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器

双亲委托机制

JavaClassLoader的加载采用了双亲委托机制,采用双亲委托机制加载类的时候采用以下的几个步骤:

  1. 当前ClassLoader首先从本身已经加载的类中查询是否此类已经加载,若是已经加载则直接返回原来已经加载的类。

  2. 当前ClassLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用一样的策略,首先查看本身的缓存,而后委托父类的父类去加载,一直到Bootstrap ClassLoader

  3. 当全部的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它本身的缓存中,以便下次有加载请求的时候直接返回。

小结 :双亲委托机制的核心思想分为两个步骤。其一,自底向上检查类是否已经加载;其二,自顶向下尝试加载类。

ClassLoader隔离问题

每一个类装载器都有一个本身的命名空间用来保存已装载的类。当一个类装载器装载一个类时,它会经过保存在命名空间里的类全局限定名(Fully Qualified Class Name)进行搜索来检测这个类是否已经被加载了。

JVM 及 Dalvik 对类惟一的识别是 ClassLoader id + PackageName + ClassName,因此一个运行程序中是有可能存在两个包名和类名彻底一致的类的。而且若是这两个”类”不是由一个 ClassLoader 加载,是没法将一个类的示例强转为另一个类的,这就是 ClassLoader 隔离。

双亲委托 是 ClassLoader类一致问题的一种解决方案,也是 Android 差价化开发和热修复的基础。

类装载器特色

Java提供了动态加载特性。在运行时的第一次引用到一个class的时候会对它进行装载(Loading) 、 连接(Linking) 和 初始化(Initialization) ,而不是在编译时进行。不一样的JVM的实现不一样,本文所描述的内容均只限于Hotspot JVM

JVM的类装载器负责动态装载,Java的类装载器有以下几个特色:

  • 层级结构:Java里的类装载器被组织成了有父子关系的层级结构。Bootstrap类装载器是全部装载器的父亲。

  • 代理模式: 基于层级结构,类的代理能够在装载器之间进行代理。当装载器装载一个类时,首先会检查它在父装载器中是否进行了装载。若是上层装载器已经装载了这个类,这个类会被直接使用。反之,类装载器会请求装载这个类

  • 可见性限制:一个子装载器能够查找父装载器中的类,可是一个父装载器不能查找子装载器里的类。

  • 不容许卸载:类装载器能够装载一个类可是不能够卸载它,不过能够删除当前的类装载器,而后建立一个新的类装载器装载。

类装载器过程

  • 加载(Loading)

    首先,根据类的全限定名找到表明这个类的Class文件,而后读取到一个字节数组中。接着,这些字节会被解析检验它们是否表明一个Class对象 并包含正确的majorminor版本信息。直接父类 的类和接口也会被加载进来。这些操做一旦完成,类或者接口对象 就从二进制表示中建立出来了。

  • 连接(Linking)

    连接是检验类或接口并准备类型和父类接口的过程。连接过程包含三步:校验(Verifying)准备(Preparing)部分解析(Optionally resolving)

    • 验证

      这是类装载中最复杂的过程,而且花费的时间也是最长的。任务是确保导入类型的准确性,验证阶段作的检查,运行时不须要再作。虽然减慢加了载速度,可是避免了屡次检查。

    • 准备

      准备过程一般分配一个结构用来存储类信息,这个结构中包含了类中定义的成员变量方法 和接口信息等。

    • 解析

      解析是可选阶段,把这个类的常量池中的全部的符号引用改变成直接引用。若是不执行,符号解析要等到字节码指令使用这个引用时才会进行。

  • 初始化(Initialization)

    把类中的变量初始化成合适的值。执行静态初始化程序,把静态变量初始化成指定的值。

JVM规范定义了上面的几个任务,不过它容许具体执行的时候可以有些灵活的变更。

2. 执行引擎(Execution Engine)

经过类装载器装载的,被分配到JVM运行时数据区的字节码会被执行引擎执行。

执行引擎 以指令为单位读取 Java 字节码。它就像一个 CPU 同样,一条一条地执行机器指令。每一个字节码指令都由一个1字节的操做码和附加的操做数组成。执行引擎 取得一个操做码,而后根据操做数来执行任务,完成后就继续执行下一条操做码

不过 Java 字节码是用一种人类能够读懂的语言编写的,而不是用机器能够直接执行的语言。所以,执行引擎 必须把字节码转换成能够直接被 JVM 执行的语言。

字节码 能够经过如下两种方式转换成机器语言

  • 解释器

    解释器 一条一条地读取字节码解释 而且 执行 字节码指令。由于它一条一条地解释和执行指令,因此它能够很快地解释字节码,可是执行起来会比较慢。这是解释执行的语言的一个缺点。字节码这种“语言”基原本说是解释执行的。

  • 即时(Just-In-Time)编译器

    即时编译器 被引入用来弥补解释器的缺点。执行引擎 首先按照 解释执行 的方式来执行,而后在合适的时候,即时编译器 把 整段字节码 编译成 本地代码。而后,执行引擎就没有必要再去解释执行方法了,它能够直接经过本地代码去执行它。执行本地代码比一条一条进行解释执行的速度快不少。编译后的代码能够执行的很快,由于本地代码是保存在缓存里的。

Java 字节码是解释执行的,可是没有直接在 JVM 宿主执行原生代码快。为了提升性能,Oracle Hotspot 虚拟机会找到执行最频繁的字节码片断并把它们编译成原生机器码。编译出的原生机器码被存储在非堆内存的代码缓存中。

经过这种方法(JIT)Hotspot 虚拟机将权衡下面两种时间消耗:将字节码编译成本地代码须要的额外时间和解释执行字节码消耗更多的时间。

这里插入一下 Android 5.0 之后用的 ART 虚拟机使用的是 AOT 机制。

Dalvik 是依靠一个 Just-In-Time (JIT)编译器去解释字节码。开发者编译后的应用代码须要经过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不一样硬件和架构上运行。ART 则彻底改变了这套作法,在应用安装时就预编译字节码到机器语言,这一机制叫Ahead-Of-Time (AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。

参考

周志明,深刻理解Java虚拟机:JVM高级特性与最佳实践,机械工业出版社


欢迎关注技术公众号: 零壹技术栈

image

本账号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

相关文章
相关标签/搜索