JavaEE - JVM Java虚拟机

JVM
大家重点掌握的是垃圾回收算法(引用计数+可达性分析+标记清除+标记整理+复制算法+分代回收)

课程目标:

1. 了解 JVM 的发展史

2. 了解 JVM 运行原理

3. 掌握 JVM 基本组成

4. 掌握 JVM 垃圾回收算法

5. 掌握类加载机制

6. 掌握 JMM


版本更新内容:

1. JVM 运行时数据区所有部分的作用做了一个说明(解决了为什么需要这些区域的问题)补充 了一些图片;

2. 方法区的实现:永久代/元空间举例说明(汽车的动能提供装置),添加了图片。

3. 添加图片:JVM 演示内存溢出时,Idea 设置的图片。

4. 新增垃圾收集器的作用,以及为什么有那么多的垃圾收集器的原因。

5. 一个对象的一生,JVM 总结和执行流程图。

6. 破坏双亲委派模型的案例(SPI- > JDBC)

1.JVM 简介

JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机。 虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。 常见的虚拟机:JVM、VMwave、Virtual Box。

JVM 和其他两个虚拟机的区别:

  • 1. VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器;
  • 2. JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进 行了裁剪。

JVM 是一台被定制过的现实当中不存在的计算机。

1.1 HotSpot VM (JDK8默认虚拟机)

HotSpot 历史

  1. 最初由一家名为“Longview Technologies”的小公司设计;
  2. 1997年,此公司被Sun收购;2009年,Sun公司被甲骨文收购。
  3. JDK1.3时,HotSpot VM成为默认虚拟机

目前 HotSpot 占用绝对的市场地位,称霸武林。

不管是现在仍在广泛使用JDK6,还是使用比较多的JDK8中,默认的虚拟机都是HotSpot;

Sun/Oracle JDK和OpenJDK的默认虚拟机。从服务器、桌面到移动端、嵌入式都有应用。

名称中的HotSpot指的就是它的热点代码探测技术。它能通过计数器找到最具编译价值的代码,触发即 时编译(JIT)或栈上替换;通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取 得平衡。

1.2 JVM 和《Java虚拟机规范》

以上的各种 JVM 版本,比如 HotSpot 和 J9 JVM,都可以看做是不同厂商实现 JVM 产品的具体实现,而 它们(JVM)产品的实现必须要符合《Java虚拟机规范》,《Java虚拟机规范》是 Oracle 发布 Java 领 域最重要和最权威的著作,它完整且详细的描述了 JVM 的各个组成部分。

PS: 本文以下部分,默认都是使用 HotSpot,也就是 Oracle Java 默认的虚拟机为前提来进行介 绍的。

2.JVM 运行流程

JVM 是 Java 运行的基础,也是实现一次编译到处执行的关键,那么 JVM 是如何执行的呢?

JVM 执行流程

程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area) ,而字节码 文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调 用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能,这就是这4个主要组成部 分的职责与功能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fOmc1g2k-1654567556752)(media/968a36a94c7c0209c5f46a7bacae2abb.png)]

总结来看, JVM 主要通过分为以下 4 个部分,来执行 Java 程序的,它们分别是:

  1. 类加载器(ClassLoader)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地库接口(Native Interface)

3.JVM 类加载

① 类加载过程

从上面的图片我们可以看出整个 JVM 执行的流程中,和程序员关系最密切的就是类加载的过程了,所以 接下来我们来看下类加载的执行流程。

对于一个类来说,它的生命周期是这样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OxDcoVcn-1654567556757)(media/520cef80a3b2bf5119194bb7b8a7ddf7.jpeg)]

其中前 5 步是固定的顺序并且也是类加载的过程,其中中间的 3 步我们都属于连接,所以对于类加载来 说总共分为以下几个步骤:

1. 加载

2. 连接

  1. 验证
  2. 准备
  3. 解析

3. 初始化

下面我们分别来看每个步骤的具体执行内容。

面试题:简述 java类加载机制?
虚拟机把描述类的数据从 Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的 java类型。

1) 加载 Loading

先找到对应的.class文件,然后打开并读取.class文件,同时初步生成一个类对象。

“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载 Class Loading 是不同的,一个是加载 Loading 另一个是类加载 Class Loading,所以不要把二者搞混了。

在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流。

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

在这里插入图片描述

2) 验证 verification

主要就是验证读到的内容是不是和规范中规定的格式,完全匹配如果发现这里读到的数据格式不符合规范,就会类加载失败,并且抛出异常

验证的目的是为了确保 Class文件的字节流中的信息不回危害到虚拟机.

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节 流中包含的信息符合《Java虚拟机 规范》的全部约束要求,保证这些信 息被当作代码运行后不会危害虚拟机自身的安全。

验证选项:

  • 文件格式验证
  • 字节码验证
  • 符号引用验证…

3) 准备 Preparation

初始化静态变量,给类中的静态变量分配内存,并且设置上0值

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。 准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在 Java堆中。

比如此时有这样一行代码:

public static int value = 123;

它是初始化 value 的 int 值为 0,而非 123。

4) 解析 Resolution

初始化常量的过程

解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程

5) 初始化 Initializing

执行类构造器方法的过程。

初始化阶段,Java 虚拟机真正开始执行类中定义的 Java 程序代码,将主导权移交给应用程序。初始化 阶段就是执行类构造器方法的过程。

② 双亲委派模型

面试题:类加载器双亲委派模型机制?

如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器没有找到所需的类时,子加载器才会尝试去加载该类。

这个东西是类加载中的一个环节.
这个环节处于Loading阶段的
双亲委派模型,描述的就是JVM中的类加载器,如何根据类的全限定名(java.lang.String )找到.class文件的过程

在这里插入图片描述

什么是双亲委派模型?

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父 类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最 终都应该传送到最顶层的启 动类加载器中,只有当父加载器反馈自己无 法完成这个加载请求(它的搜索范围中没有找到所需的类) 时,子加载器才会尝试自己去完成加载。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a7Y6CkWa-1654567556758)(media/2f437fbe8f07b80b5ae379b0a6974546.jpeg)]

面试题:什么是类加载器,类加载器有哪些?
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)用来加载 java核心类库,无法被 java程序直接
    引用。
  2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的
    实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  3. 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)
    来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
    ClassLoader.getSystemClassLoader()来获取它。
  4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现

双亲委派模型的优点

1. 避免重复加载类:比如 A 类和 B 类都有一个父类 C 类,那么当 A 启动时就会将 C 类加载起来,那 么在 B 类进行加载时就不需要在重复加载 C 类了。

2. 安全性:使用双亲委派模型也可以保证了 Java 的核心 API 不被篡改,如果没有使用双亲委派模 型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类,而有些 Object 类又是用户 自己提供的因此安全性就不能得到保证了。

③ 破坏双亲委派模型

双亲委派模型虽然有其优点,但在某些情况下也存在一定的问题,比如 Java 中 SPI(Service Provider Interface,服务提供接口)机制中的 JDBC 实现。

小知识:SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的
接口,它可以用来启用框架扩展和替换组件。 SPI 的作用就是为这些被扩展的 API 寻找服务实 现。

JDBC 的 Driver 接口定义在 JDK 中,其实现由各个数据库的服务商来提供,比如 MySQL 驱动包。我们 先来看下 JDBC 的核心使用代码:

public class JdbcTest {
   
    public static void main(String[] args){
   
        Connection connection = null;
        try {
   
            connection = 
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", 
"awakeyo");
        } catch (SQLException e) {
   
            e.printStackTrace();
        }
        System.out.println(connection.getClass().getClassLoader());
        System.out.println(Thread.currentThread().getContextClassLoader());
        System.out.println(Connection.class.getClassLoader());
    }
}

然后我们进入 DriverManager 的源码类就会发现它是存在系统的 rt.jar 中的,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5XfvCHlg-1654567556759)(media/9e9f3ad157bd358d9f7bfbe6d65c2ad7.png)]

由双亲委派模型的加载流程可知 rt.jar 是有顶级父类 Bootstrap ClassLoader 加载的,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NhHjSxYP-1654567556759)(media/3a79d10d60b8dd610b09614143cd4501.jpeg)]

而当我们进入它的 getConnection 源码是却发现,它在调用具体的类实现时,使用的是子类加载器(线 程上下文加载器 Thread.currentThread().getContextClassLoader )来加载具体的数据库数据库包(如 mysql 的 jar 包),源码如下:

@CallerSensitive
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
   
    return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws 
SQLException {
   
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值