JVM学习笔记一:JVM概览

JVM学习笔记一:JVM概览

前言

作为一名Java开发程序员,基本上每天都在使用Java,但对JVM的了解属实匮乏,包括在看一些八股文的时候也会提到一些JVM相关的面试题,但终究是没去了解过,对相关的回答看的也是一知半解,所以下定决心来学习一下JVM,也供各位小伙伴们参考学习

简介

既然我们要学习JVM,那么我们首先要知道 JVM是什么?背过八股文的小伙伴儿肯定都知道,Java里包含了三大核心组件:JDK、JRE 和 JVM,那么我们就先来说说 JDK

JDK

JDK 的全名叫做 Java Development Kit,也就是 Java开发工具集,里面包含了一些我们 需要的开发工具,以及运行环境,也就是 JRE

JRE

JRE 的全名叫做 Java Runtime Environment,也就是 Java运行环境,被JDK 包含在内。它提供了Java的运行环境,我们的代码才能正常运行,其中包含了 Java的核心类库和JVM的标准实现。

JVM

JVM 的全名叫做 Java Virtual Machine,也就是Java虚拟机,我们的代码就在它上面所运行,也是我们 Java能够实现跨平台的最核心的组件

JDK、JRE、JVM三者之间的关系

未命名文件 (1).png

看图可以知道:

  • JDK : JRE+其他组件
  • JRE : JVM+其他组件

JDK 包含 JRE,在 JRE 中包含 JVM。

JVM的生命周期

简述完了 JDK、JRE 和 JVM 三者之间的关系,我们就开始正式来学习 JVM 了,那么所有的对象都会有生命周期,JVM 也不例外,接下来我们用一张图来看一下 JVM的生命周期。

未命名文件.png

详细分析

加载

根据上面我们提到的生命周期,再来画一张简易的图来便于理解

未命名文件.png

在虚拟机规范中并没有强制约束类必须在什么时候加载,而是 交给虚拟机的具体实现来控制。在 JVM 中是 以懒加载的形式来加载类(懒汉模式),也就是什么时候用到了什么时候才会去加载。

在加载阶段虚拟机必须要完成三件事:

  1. 通过 类的全限定名 来获取定义这个类的 二进制字节流
  2. 将字节流所代表的静态存储结构 转换为元数据空间(JDK8之前叫做方法区)的运行时数据结构
  3. 在堆中生成一个代表该类的Class对象,以便于通过该对象访问上面提到的元数据空间的数据结构

注意:二进制字节流除了从class文件中获取之外,还可以通过以下方式获取:

  • 从网络中获取
  • 从zip、jar、ear、wra等不同的格式文件中获取
  • 从数据库读取
  • 运行时计算(例如动态代理)

验证

上面我们提到的生命周期的图里很清晰的看到,加载过后是验证,也是 连接的第一个步骤。它的作用是 校验加载进来的class文件是否合法

未命名文件.png

在验证时大致会分为四个阶段:

  • 文件格式校验:主要 验证字节流是否复合class文件的格式规范
  • 元数据校验:主要对字节码描述的信息进行 语义分析以确保其描述信息符合Java语言规范
  • 字节码校验:通过分析 数据流控制流 ,确保程序的语义是合法且符合逻辑的,继而 保证被校验的类在运行时不会危害虚拟机的行为
  • 符号引用验证校验类是否缺少或被禁止访问其依赖的某些外部类、方法和字段等资源,确保解析阶段可以正常执行

准备

在验证完毕后,会先给 以及 变量 等资源 分配内存空间。这些类使用的内存都在 方法区 中分配

需要注意的是:此时的内存分配只包括 类变量,不包括实例变量,因为实例变量会在对象实例化的时候随着对象一起分配在Java的堆中。且此时会给变量赋一些默认的零值,例如:0、0L(Long)、true(Boolean)、null(Object) 等,而不是Java代码中显示赋予的值

未命名文件.png

解析

在解析阶段,虚拟机会将常量池中的符号引用替换成直接引用(内存地址)

在JVM中,符号引用是指使用一个符号来代替目标引用的一种引用方式

未命名文件.png

初始化

初始化阶段是 类加载过程中的最后一步,此时开始 真正执行类中定义的代码(字节码) ,初始化阶段就是执行一个class中的 static语句 以及所有 类变量 的赋值操作。如果一个类中没有 static语句 和 对 变量 赋值的操作,那么编译器可以不为其执行初始化方法。

未命名文件.png

需要注意的是:在虚拟机规 范中规定了有且仅有6种情况,在类还未初始化时必须立即对类进行初始化:

  • 遇到 newgetstaticputstaticinvokestatic 等字节码指令时例如:new对象时、读取或设置类的静态字段(被final修饰,在编译器已经把结果放入常量池的静态字段除外)时、调用静态方法时等)

  • 使用java.lang.reflect包的方法对类进行反射调用时

  • 初始化一个类时,其父类还没初始化时,需要先触发父类初始化

  • 虚拟机启动时,用户需要执行 main方法 的类,虚拟机会先初始化该类

  • 在JDK7时加入了 MethodHandle 方法和 VarHandle 方法,这两个类相当于要给轻量级的反射调用机制,想使用这两个调用时,必须先使用 findStaticVarHandle 方法来初始化要调用的类

  • default 关键字修饰的接口,如果该接口的实现类执行了初始化,则该接口需要在其实现类初始化前被初始化

自定义类加载器会涉及到 JVM双亲委派机制 ,它的逻辑是 先顺着继承结构向上,由父类加载所需要的类。当父类没找到要加载的类时,再顺着继承结构向下找,由子类加载

未命名文件.png

在Java中共有四种类加载器:

  1. 启动类加载器(Bootstrap ClassLoader):用于加载Java中lib中的类
  2. 扩展类加载器(Extension ClassLoader):用于加载 lib 或者 ext 中的类
  3. 应用程序加载器(Application ClassLoader):用于加载ClassPath中的环境变量,所指定路径中的类
  4. 自定义类加载器:用户自定义加入的类加载器

使用

这个阶段包括了 主动引用(例如初始化)被动引用注意:被动引用不会引起类的初始化

卸载

一个类什么时候结束生命周期,取决于它的 Class对象什么时候结束生命周期。由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期内始终不会被卸载。

当类满足以下情况时会被卸载掉:

  • 当加载该类的类加载器已经被回收时
  • JVM堆中不存在该类的任何实例时
  • 该类对应的Class对象没有被引用时