JVM学习笔记一:JVM概览
![](https://storage.bummon.com//image/202307222200027.png)
JVM学习笔记一:JVM概览
BummonJVM学习笔记一: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三者之间的关系
看图可以知道:
- JDK : JRE+其他组件
- JRE : JVM+其他组件
JDK 包含 JRE,在 JRE 中包含 JVM。
JVM的生命周期
简述完了 JDK、JRE 和 JVM 三者之间的关系,我们就开始正式来学习 JVM 了,那么所有的对象都会有生命周期,JVM 也不例外,接下来我们用一张图来看一下 JVM的生命周期。
详细分析
加载
根据上面我们提到的生命周期,再来画一张简易的图来便于理解
在虚拟机规范中并没有强制约束类必须在什么时候加载,而是 交给虚拟机的具体实现来控制。在 JVM 中是 以懒加载的形式来加载类(懒汉模式),也就是什么时候用到了什么时候才会去加载。
在加载阶段虚拟机必须要完成三件事:
- 通过 类的全限定名 来获取定义这个类的 二进制字节流
- 将字节流所代表的静态存储结构 转换为元数据空间(JDK8之前叫做方法区)的运行时数据结构
- 在堆中生成一个代表该类的Class对象,以便于通过该对象访问上面提到的元数据空间的数据结构
注意:二进制字节流除了从class文件中获取之外,还可以通过以下方式获取:
- 从网络中获取
- 从zip、jar、ear、wra等不同的格式文件中获取
- 从数据库读取
- 运行时计算(例如动态代理)
验证
上面我们提到的生命周期的图里很清晰的看到,加载过后是验证,也是 连接的第一个步骤。它的作用是 校验加载进来的class文件是否合法。
在验证时大致会分为四个阶段:
- 文件格式校验:主要 验证字节流是否复合class文件的格式规范
- 元数据校验:主要对字节码描述的信息进行 语义分析,以确保其描述信息符合Java语言规范
- 字节码校验:通过分析
数据流
和控制流
,确保程序的语义是合法且符合逻辑的,继而 保证被校验的类在运行时不会危害虚拟机的行为 - 符号引用验证:校验类是否缺少或被禁止访问其依赖的某些外部类、方法和字段等资源,确保解析阶段可以正常执行
准备
在验证完毕后,会先给 类
以及 变量
等资源 分配内存空间。这些类使用的内存都在 方法区 中分配
需要注意的是:此时的内存分配只包括 类变量,不包括实例变量,因为实例变量会在对象实例化的时候随着对象一起分配在Java的堆中。且此时会给变量赋一些默认的零值,例如:0、0L(Long)、true(Boolean)、null(Object) 等,而不是Java代码中显示赋予的值
解析
在解析阶段,虚拟机会将常量池中的符号引用替换成直接引用(内存地址)
在JVM中,符号引用是指使用一个符号来代替目标引用的一种引用方式
初始化
初始化阶段是 类加载过程中的最后一步,此时开始 真正执行类中定义的代码(字节码) ,初始化阶段就是执行一个class中的 static语句
以及所有 类变量
的赋值操作。如果一个类中没有 static语句
和 对 变量
赋值的操作,那么编译器可以不为其执行初始化方法。
需要注意的是:在虚拟机规 范中规定了有且仅有6种情况,在类还未初始化时必须立即对类进行初始化:
遇到
new
、getstatic
、putstatic
、invokestatic
等字节码指令时例如:new对象时、读取或设置类的静态字段(被final修饰,在编译器已经把结果放入常量池的静态字段除外)时、调用静态方法时等)使用java.lang.reflect包的方法对类进行反射调用时
初始化一个类时,其父类还没初始化时,需要先触发父类初始化
虚拟机启动时,用户需要执行
main方法
的类,虚拟机会先初始化该类在JDK7时加入了
MethodHandle
方法和VarHandle
方法,这两个类相当于要给轻量级的反射调用机制,想使用这两个调用时,必须先使用findStaticVarHandle
方法来初始化要调用的类被
default
关键字修饰的接口,如果该接口的实现类执行了初始化,则该接口需要在其实现类初始化前被初始化
自定义类加载器会涉及到 JVM 的
双亲委派机制
,它的逻辑是 先顺着继承结构向上,由父类加载所需要的类。当父类没找到要加载的类时,再顺着继承结构向下找,由子类加载。
在Java中共有四种类加载器:
- 启动类加载器(Bootstrap ClassLoader):用于加载Java中lib中的类
- 扩展类加载器(Extension ClassLoader):用于加载 lib 或者 ext 中的类
- 应用程序加载器(Application ClassLoader):用于加载ClassPath中的环境变量,所指定路径中的类
- 自定义类加载器:用户自定义加入的类加载器
使用
这个阶段包括了 主动引用(例如初始化) 和 被动引用,注意:被动引用不会引起类的初始化
卸载
一个类什么时候结束生命周期,取决于它的 Class对象什么时候结束生命周期。由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期内始终不会被卸载。
当类满足以下情况时会被卸载掉:
- 当加载该类的类加载器已经被回收时
- JVM堆中不存在该类的任何实例时
- 该类对应的Class对象没有被引用时