聊聊Java虚拟机(一)—— 类加载子系统

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 虚拟机就是一款用来执行虚拟计算机指令的计算机软件。它相当于一台虚拟计算机。大体上,虚拟机分为系统虚拟机和程序虚拟机。系统虚拟机就相当于一台物理电脑,里面可以安装操作系统;程序虚拟机是为了执行单个计算机程序而设计出来的虚拟机。其中 Java 虚拟机就是**执行 Java 字节码指令的虚拟机**。

1. 前言

​ 虚拟机就是一款用来执行虚拟计算机指令的计算机软件。它相当于一台虚拟计算机。大体上,虚拟机分为系统虚拟机和程序虚拟机。系统虚拟机就相当于一台物理电脑,里面可以安装操作系统;程序虚拟机是为了执行单个计算机程序而设计出来的虚拟机。其中 Java 虚拟机就是执行 Java 字节码指令的虚拟机

JVM 是什么?

java 虚拟机是运行在各大平台的执行字节码文件的虚拟计算机。如下图所示

这样的设计可以让编译后的代码在各个操作平台上运行。

JVM 的位置

JVM 在 Java 体系结构中的位置

从用户操作角度看 JVM 所处的位置

JVM 与实际的计算机硬件没有交互,它们中间还有个操作系统,调用硬件需要通过操作系统来实现。

JVM 的结构

JVM 的整体运行结构

本文主要针对于 Hotspot VM 来进行,其结构如下所示:

JVM 的指令结构

JVM 是基于栈的指令集架构,跟基于寄存器的指令集不同。它是一个步骤一个一条指令。

//基于栈的指令集
iconst_2 //常量2入栈
istore_1
iconst_3 // 常量3入栈
istore_2
iload_1
iload_2
iadd //常量2/3出栈,执行相加
istore_0 // 结果5入栈
//基于寄存器的指令集
mov eax,2 //在eax寄存器中的值设为2
add eax,3 //将eax寄存器中的值加3

举例说明:

/**
 * @author wjw
 * @date 11/3/2021
 */
public class StackStruTest {
   
   
    public static void main(String[] args) {
   
   
        int i = 2 + 3;
        /**
         *0: iconst_5
         *1: istore_1
         *2: return
         */
        int i = 2;
        int j = 3;
        int k = i + j;
        /**
         * 0: iconst_2
         * 1: istore_1
         * 2: iconst_3
         * 3: istore_2
         * 4: iload_1
         * 5: iload_2
         * 6: iadd
         * 7: istore_3
         * 8: return
         */
        try {
   
   
            Thread.sleep(6000);
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        }
        System.out.println("hello");
    }
}

JVM 的生命周期

JVM 的启动

Java 的启动是通过引导类加载器(BootStrap ClassLoader)创建一个初始类(java.lang.class)来实现的,也就是后面要提到的子类加载器过程。

JVM 的执行

执行 Java 的字节码文件中的各个方法和类的过程。

JVM 的退出

  • 程序正常执行结束
  • 操作系统终止 JVM 进程终止
  • 某线程调用 Runtime.exit() 或 System.halt() 方法
  • 程序执行过程异常或错误而终止退出

2. 类加载子系统的主要流程

让我们先看看一个 java 程序执行的流程是怎样的:

  • 首先一个 java 文件编写完后,经过 java 编译器生成 .class 文件
  • 这个 .class 文件经过类加载器,加载各类的 jar 包,以及该程序所需要的三方类的 .class 文件。并生成 java.lang.class 对象,这个对象将作为程序访问方法区中的这些类型数据的外部接口
  • . class 文件经过解析执行和 JIT 编译器编译执行,并调用操作系统相关指令来完成 java 程序。

而类加载子系统涉及到的过程有类加载器、字节码校验器和翻译字节码这几个过程:

加载阶段(Loading)

加载阶段(Loading)是类加载阶段(Class Loading)的一部分。在加载阶段中,.class 文件将经过三个类加载器的加载后才能进入下一个阶段。该阶段主要的作用(也就是上面 java 程序执行中的类加载器阶段)有:

  • 将各种各样格式的 .class 文件(jar 包,网络中,动态代理等等)读取并生成一个字节流,并转换成方法区中的运行时数据结构
  • 在内存中生成一个 java.lang.Class 对象,这个对象将作为程序访问方法区中的这些类型数据的外部接口

在类加载器中,主要分为两类:引导类加载器自定义类加载器

引导类加载器(Bootstrap ClassLoader)
  • 使用的是 C/C++ 来实现的,无法访问到,调用getClassLoader() 函数是为null。

  • 加载的类是一些核心类库,lib/home 下的类库。比如说是 Object 、String 等等 JVM 自身运行需要的类

  • 是其他加载器的父类吗?是的,但是无父类加载器

自定义类加载器(User-Defined ClassLoader)

派生于抽象类 ClassLoader 的类加载器

  • (1)拓展类加载器(Extension ClassLoader)

    • 父类加载器为 BootStrap ClassLoader

    • 主要加载的类是ext 文件夹下的类库

  • (2)应用程序类加载器(AppClassLoader)

    • 父类加载器为 Extension ClassLoader

    • 主要加载的是环境变量 classpath 或系统属性 java.class.path 指定路径下的类库

    • 程序中默认的类加载器

  • (3) 自定义类加载器

    • extCassLoader 和 AppClassLoader 是 sun.misc.Launcher 的两个内部类

为了防止虚拟机内部的核心类库的修改和非法调用,JVM 的设计者们在类加载过程中设计出了双亲委派机制沙箱安全机制

双亲委派机制
  • 当前类加载器先不加载,先委托其父类加载器,依次到启动加载器
  • 如果启动加载器还没找到所要的类,然后向下查找,到返回到最初的子类加载器
  • 类加载器之间的关系不是继承,而是通过组合来复用父加载器的代码。

举例

  • 首先在 Bootstrap ClassLoader 中加载核心 rt.jar 包中的核心类
  • 核心类中的一些三方接口在反向委派到应用程序加载器加载第三方的 jar 包
  • 是通过当前线程加载器(就是系统类加载器)加载到 jdbc.jar 包中的 SPI 接口实现类

双亲委派机制的优点:

  • 避免类的重复加载
  • 保护程序安全,防止核心 API 被随意篡改
沙箱安全机制

引导类加载器在加载时会首先加载 JDK 中自带的文件。报错信息中没有相应方法时的机制。保证对 java 核心源代码的保护。

链接阶段(Linking)

验证(Verify)

验证字节流文件中的相关信息,确认当前文件符合虚拟机的相关格式(文件、元数据、符号等等)。

准备(Prepare)

在方法区中为类变量分配内存并设置类变量初始值

  • 这里的类变量是指被 static 修饰的变量,不包括实例变量(实例变量在对象实例化时随着对象一起分配)
  • 初始值指的是数据类型的零值
解析(Resolve)
  • 虚拟机将常量池中的符号引用转换成直接引用(class 文件很小,先存放指向所需要类的符号,解析时再引用)

  • 针对类、接口、字段方法等7类符号引用进行转换

  • 没有规定解析阶段发生的具体时间,也可能在初始化阶段的后面

初始化阶段(Initiation)

  • 初始化过程实际上就是执行类构造器方法 ( ) 的过程

    • 它是 javac 编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来,不需要定义。
    public class ClassInitTest {
         
         
        private static int num = 1;
    
        static {
         
         
            num = 2;
            number = 20;
            System.out.println(num);//因为在linking 阶段中的 Prepare 阶段已经给静态变量赋过初始值了。
            //非法的前向引用;在声明变量之前,可以去给这些变量进行赋值,但是不能够调用它。
            //System.out.println(number);//会报错误
        }
    
        /**也就是在 linking 中的 prepare 中对变量进行默认赋值为0
         * 此时initial 给予其一个覆盖从 20 到 10
         *
         */
        private static int number = 10;
    
        public static void main(StringDemo[] args)
        {
         
         
            System.out.println(ClassInitTest.num);
            System.out.println(ClassInitTest.number);
        }
    }
    
    • 它不需要显示调用父类构造器,在子类的( ) 执行前,父类的 ( ) 方法早已执行完毕。

  • 构造器方法执行是按语句顺序来执行的。
  • 类构造器方法 ( ) 和类构造器( ) 并不是一回事

    • ( ) 方法不需要定义,而( ) 需要定义(不定义但是会有个默认为空的构造器方法)
    • 只有当代码中包含 static 变量时,才会执行 ( ) 方法,而( ) 所有类的字节码中都有


  • 在多线程时,初始化只执行一次,否则会被上锁

3.其他小结

  • 确定两个类是否相同
    • 包名、类名完全相同
    • 所用的类加载器也要相同
  • 类的主动使用和被动使用
    • 也就是是否有初始化过程,是否调用 clinit<>() 方法
目录
相关文章
|
1月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
42 3
|
2月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
112 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
3月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
27 3
|
3月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
167 0
|
4月前
|
存储 缓存 自然语言处理
(三)JVM成神路之全面详解执行引擎子系统、JIT即时编译原理与分派实现
执行引擎子系统是JVM的重要组成部分之一,在JVM系列的开篇曾提到:JVM是一个架构在平台上的平台,虚拟机是一个相似于“物理机”的概念,与物理机一样,都具备代码执行的能力。
|
3月前
|
设计模式 存储 安全
18 Java反射reflect(类加载+获取类对象+通用操作+设计模式+枚举+注解)
18 Java反射reflect(类加载+获取类对象+通用操作+设计模式+枚举+注解)
95 0
|
4月前
|
存储 前端开发 Java
(二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器
上篇《初识Java虚拟机》文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再交由执行引擎执行。本文中则会对Java虚拟机的类加载机制以及执行引擎进行全面分析。
|
4月前
|
Java
java通过idea启动查看类加载来源信息
java通过idea启动查看类加载来源信息
137 0
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
6天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。