JVM 三 类装载系统

2024-03-24 16:48
文章标签 java 系统 jvm 装载

本文主要是介绍JVM 三 类装载系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

导读

 

在之前的文章中,我们通过一张图的方式(图?)整体上了解了JVM的结构,并重点讲解了JVM的内存结构、内存回收算法及回收器方面的知识。收到了不少读者朋友们的反馈和指正,在这里作者向这些提出中肯建议的读者朋友们表示感谢,谢谢你们的支持。

 

在今天的文章中将主要和大家一起探讨关于类装载子系统的内容。我们知道,Java源代码(.java文件)需要通过编译器编译成字节码文件(.class)后由类装载子系统(ClassLoader)载入运行时数据区(<jdk1.8之前是载入方法区,>=jdk1.8以后是载入元数据区)才能被后续的Java运行程序(线程)正常使用(实例化或引用)。

 

那么类装载的具体机制是什么样的呢?下面就让我们一起进一步来了解下吧!

 

JVM类装载概述

 

与C/C++那些需要在编译器期进行连接工作的语言不同,Java类的加载、连接和初始化都是在程序运行时完成的,只有在类被需要的时候才进行动态加载,这种方式被称为“Java语言的运行期类加载机制”

 

例如我们在实际使用Java语言进行编程时通常会编写一个面向接口的应用程序,可以等到运行时再指定其实际的实现类。此外,更高级一点的做法是可以通过自定义的类加载器(关于具体的ClassLoader在后面的内容会提到),让一个本地的应用程序可以在运行时从网络或其他地方加载一个二进制流作为程序代码的一部分(Applet就是这么干的),这种组装应用程序的方式目前已广泛的应用于Java程序之中。

 

类(Class)从被加载到虚拟机内存中开始,到卸载出内存为止会经历如下生命周期:

 

 

其中验证、准备、解析3个部分又统称为连接(Linking)。

在以上过程中,除解析外,加载、验证、准备、初始化、卸载这5个阶段的顺序都是确定的,

JVM规定类的加载过程必须按照这种顺序按部就班地开始。而解析阶段则不一定,为了支持Java语言的运行时绑定,解析过程在某些情况下可以在初始化阶段之后再开始。

需要注意的是,这些阶段并不是必须等到上一个阶段完成才能开始下一个阶段,这些阶段通常都是互相交叉地混合式进行的,会在一个阶段执行的过程中就会调用、激活另外一个阶段。

 

聊到这里,我们大概了解了从一个class字节码文件变成加载到内存中能够被使用的类,按照先后顺序需要经过加载、连接、初始化三大主要步骤。连接过程又需要经历验证、准备、解析三个阶段,完成后类被加载至内存,但此时并不能被使用,还需要经过初始化阶段。

 

那么,在Java中是否所有的类型在类加载的过程中都需要经过这几个步骤呢?

 

我们知道Java语言的类型可以分为两大类:基本类型、引用类型

基本类型是由虚拟机预先定义好的,所以不会经历单独的类加载过程。

而引用类型又分为四种:类、接口、数组类、泛型参数。由于泛型参数会在编译的过程中被擦除(关于类型擦除的知识,大家可以查下资料),所以在Java中只有类、接口、数组类三种类型需要经历JVM对其进行连接和初始化的过程。

 

在上述三种类型中数组类是由JVM直接生成的,类和接口则有对应的字节流,字节流最常见的形式就是我们由编译器生成的class文件,另外的形式也有在前面说到的通过网络加载的二进制流(例如网页中内嵌的小程序 Java applet),这些不同形式的字节流都会被JVM加载到内存中,成为类或接口。

 

JVM类装载过程

 

那么这些过程具体会干些什么事呢?接下来我们就详细了解下这些具体步骤的细节。

 

加载(Loading)

 

“加载”是“类加载”(Class Loading)过程的一个阶段,是查找字节流并据此创建类的过程。在前面我们提到过说数组类因为没有对应的字节流所以是由JVM直接生成的,而对于类和接口来说则需要借助类加载器(后面会讲到)来完成查找字节流的过程。

 

但是我们在回答上面关于Java中哪些类型需要经历类加载的阶段时,又明确说了数组类型也是需要JVM对其进行连接和初始化的,这是不是有点矛盾呢?事实上,虽然数组类本身并不通过类加载器加载(由虚拟机直接创建),但是数组类与类加载器仍然有很密切的关系,因为数组类的元素类型(Element Type)如对象数组,最终还是要靠类加载器去创建。

 

关于数组类的加载创建过程是需要遵循如下规范的:

  • 如果数组的元素类型是引用类型的话,那么就会递归采用前面内容中定义的类加载过程去加载这个元素类型,该数组本身将会在加载该元素类型的类加载器的类名称空间上被标识(这一点非常重要,在讲述类加载器的时候会介绍到,一个类必须与类加载器一起确定唯一性)。

  • 如果数组的元素类型不是引用类型(例如int[]数组),JVM则会把该int[]数组标记为启动类加载器(Bootstrap Classloader)关联。

 

实际上,在加载阶段虚拟机需要完成以下三件事:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转换为方法区(JDK1.8以前)或者元数据(JDK1.8以后)的运行时数据结构。
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区或元数据区这个类的各种数据的访问入口。

加载阶段完成后,JVM外部的二进制字节流就按照虚拟机所需要的格式存储在了方法区或元数据区的内存中了。

 

验证(Verification)

 

验证是连接阶段的第一步,这一阶段的主要目的是为了确保Class文件的字节流中所包含的信息符合当前JVM的要求,并且不会危害JVM自身的安全。虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证阶段是非常重要的,这个阶段是否严谨,直接决定了JVM是否能承受恶意代码的攻击。

 

那么验证阶段具体应当检查哪些方面?如何检查?何时检查呢?

 

《Java虚拟机规范(Java SE 7版)》中大概是有130页左右的篇幅是来描述验证的过程的,受篇幅所限,我们无法逐条规则去探讨。但从整体上来看,验证阶段大致会完成如下四个阶段的检验动作:

 

1)、文件格式验证

 

文件格式验证就是要验证字节流是否符合Class文件格式的规范,以及是否能被当前版本的虚拟机处理。例如,常量池的常量中是否有不被支持的常量类型;Class文件中各个部分及文件本身是否有被删除的或附加的其他信息等等。

 

这个阶段验证的主要目的就是为了保证输入的字节流能正确地解析并存储于方法区或元数据区之内,格式上符合描述一个Java类型信息的要求。本阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证,字节流才会正常进入内存(方法区/元数据区)中进行存储,所以后面剩下的3个验证阶段全部是基于已经载入内存的存储结构进行的,而不会再直接操作字节流了。

 

2)、元数据验证

 

元数据区验证的主要目的是对类的元数据信息进行语义的校验,以保证描述的信息符合Java语言规范的要求。

 

例如:

 

这个类是否有父类(除了java.lang.Object之外,所有的类都应该有父类)?这个类的父类是否继承了不允许被继承的类(如被final修饰的类)?如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法?类中的字段、方法是否与父类产生矛盾?等等。虽然这些逻辑目前编译器都会在编译字节码文件时加以校验,但是作为JVM类加载本身为了确保自身的安全性,也是需要进行严格校验的。

 

3)、字节码验证

 

字节码验证是一个更加复杂的阶段,主要目的是通过数据流和控制流的分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段主要是完成了对元数据信息的类型校验,而这个阶段则是对类的方法体进行校验分析,确保被校验类的方法在运行时不会做出危害虚拟机安全的事件。例如,保证方法体中的类型转换是有效的,可以把一个子类对象赋值给父类的数据类型(上溯造型),但是不能把父类对象赋值给子类数据类型或者把对象赋值给与与它毫无继承关系的类型。

 

4)、符号引用验证

 

符号引用验证可以看做是对类自身以外(主要是常量池中的各种符号引用)的信息进行匹配性校验。目的是确保后面进入解析阶段后,解析动作能够正常执行。如果无法通过符号引用验证,就会抛出如“java.lang.IllegalAccessError”、“java.lang.NoSuchFileIdError”、“java.lang.NoSuchMethodError”等这样的异常信息。

 

对于JVM的类加载机制来说,验证阶段是一个非常重要,但是不一定必要(对程序的运行期没有影响)的阶段。如果所运行的全部代码,包括自己编写的以及第三方包中的代码都已经被反复使用和验证过,那么就可以考虑使用

-Xverify:none”参数来关闭大部分的验证措施,以缩短虚拟机类加载的时间。

 

准备(Preparation)

 

准备阶段是正式为类变量(被static修饰的变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区(<Jdk1.8)元数据区(>=Jdk1.8)中进行分配。这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化的时候随对象一起分配在Java堆中。

 

另外,上面所说的对类变量进行初始值,通常情况下是初始为零值。如int类型的类变量,初始值就是0。

 

解析(Resolution)

 

在class文件被加载至JVM之前,这个类是无法知道其他类及方法、字段所对应的具体地址的,甚至不知道自己方法、字段的内存地址。因此,每当需要引用这些成员时Java编译器会生成一个符号引用。在运行阶段这个符号引用一般都能无歧义地定位在具体目标上。举个例子,对于一个方法调用,编译器会生成一个包含目标方法所在类的名字、目标方法的名字、接收参数类型以及返回值类型的符号引用,来指代所要调用的方法。

 

解析阶段的目的就是将这些符号引用解析成为实际引用。而实际引用就是真正指向内存地址的指针、相对偏移量或能间接定位到目标的句柄。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄调用点限定符这7类符号引用进行。

 

在前面我们提到过,解析阶段并不一定会在连接过程中完成,因为JVM虚拟机规范并没有对此作明确的要求,只是规定了:“如果某些字节码使用了符号引用,那么在执行这些字节码之前,需要完成对这些符号引用的解析”。对于这一点大家不要搞错了。
 

初始化(Intialization)

 

类初始化是类加载过程的最后一步,是为标记为常量值的字段赋值,以及执行<clinit>方法的过程。那么什么样的字段才会被标记为常量值呢?<clinit>方法又是什么呢?

 

在Java代码中如果要初始化一个静态字段,我们可以在声明时直接赋值,也可以在静态代码块中对其赋值。在这里,如果直接赋值的静态字段被 final 所修饰,并且它的类型是基本类型或字符串时,那么该字段便会被 Java 编译器标记成常量值(ConstantValue),其初始化直接由Java 虚拟机完成。

 

而除此之外的直接赋值操作,以及所有静态代码块中的代码则都会被Java编译器置于同一方法中,这个方法就是<clinit>方法,也称为类构造器方法。Java 虚拟机会通过加锁来确保类的 <clinit> 只会被执行一次。

 

在我们讲述JVM类加载过程的时候,并没有特别说明什么情况下需要开始类加载过程的第一个阶段:加载?这是因为JVM虚拟机规范并没有进行强制约束。但是对于初始化阶段,JVM规范则是严格规定了发生如下情况时必须立刻对类进行“初始化”,而加载、验证、准备也自然需要在此之前开始。

 

这几种情况如下:

 

1)、当虚拟机启动时,初始化用户指定的主类(包含main方法的类);

2)、当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;

3)、当遇到调用静态方法的指令时,初始化该静态方法所在的类;

4)、当遇到访问静态字段的指令时,初始化该静态字段所在的类;

5)、子类的初始化会先触发父类的初始化(如果父类还没有进行过初始化的话);

6)、如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;

7)、使用反射 API 对某个类进行反射调用时,初始化这个类;

8)、当初次调用 MethodHandle 实例时(JDK1.7的动态语言支持),初始化该 MethodHandle 指向的方法所在的类;

 

以上基本上就是类加载机制中初始化的大致过程,只有当初始化完成之后,类才能正式成为可执行的状态。

 

类加载器

 

在整个类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余的动作完全是由虚拟机主导和控制的。那么什么是类加载器呢?

 

在上述类加载机制的第一个阶段:"加载"中,把“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作由JVM外部实现的代码模块称为“类加载器”

 

从JVM的角度来看,只存在两种不同的类加载器:启动类加载器(Bootstrap ClassLoader)、其他类的加载器。启动类加载器是由C++语言实现的,属于JVM自身的一部分,而其他的类加载器则都是独立于JVM外部,由Java语言实现的继承java.lang.ClassLoader的类型。

 

而从Java程序员的角度看,类加载器还可以划分得更加细致一些。示意图如下:

 

 

在上图中的类加载器,是有层次关系的,这种关系被称之为类加载器的“双亲委派模式”,它要求除了顶层启动类加载器外,其余所有的类加载器都应当有自己的父类加载器,并且如果一个类加载器在收到类加载的请求之后都要先把这个请求委派给父类加载器去完成(每一个层次的类加载器都是如此,因此所有的加载请求最终都应该会传送到顶层的启动类加载器中),只有当父类加载器反馈自己无法完成这个加载请求(在搜索范围没有找到所需的类)时,子加载器才会尝试自己去加载。

 

双亲委派模式不是强制性的约束模型,只是Java设计者推荐给开发者的类加载器的实现方式,但是采用这种模式对于保证Java程序的稳定运作确实很重要的,因为它可以避免Java体系中基础的类型被混乱加载的风险。例如类java.lang.Object,它存放在rt.jar之中,无论那一个类加载器要加载这个类,最终都会委派给启动类加载器,这样Object类在程序的各种类加载器环境中都是一个类,否则就会导致系统中出现多个不同的Object类,从而连Java类型体系中最基本的行为都无法保证。

 

以上就是关于JVM类加载系统的全部内容了,希望本文能够对你补充知识盲点起到一点作用!

 

原文地址:https://mp.weixin.qq.com/s/UU4qltVgRsj0SG7YmER-Qw

 

 

 

这篇关于JVM 三 类装载系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/842185

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,