Java基础之类加载器

Java类加载器是用户程序和JVM虚拟机之间的桥梁,在Java程序中起了至关重要的作用,理解它有利于我们写出更优雅的程序。本文首先介绍了Java虚拟机加载程序的过程,简述了Java类加载器的加载方式(双亲委派模式),然后介绍了几种常见的类加载器及其适用场景,最后则一个例子展示了如何自定义类加载器。本文很多地方参考了java官方文档关于虚拟机加载的教程,点此直达官方参考文档

基本概念

基本文件类型和概念

常见概念介绍:

JAVA类加载示例图

  1. java源文件(.java):.java是Java的源文件后缀,里面存放程序员编写的功能代码,只是一个文本文件,不能被java虚拟机所识别, 但是java语法有其自身的语法规范要求,不符合规范的java程序应该在编译期间报错。

  2. java字节码文件(.class):可以由java文件通过 javac这个命令(jdk本身提供的工具)编译生成,本质上是一种二进制文件,这个文件可以由java虚拟机加载(类加载),然后进java解释执行, 这也就是运行你的程序。
    java字节码文件(.class文件)看起来有点多余,为什么java虚拟机不能直接执行java源码呢?主要是为了实现 多语言支持性:java虚拟机本身只识别.class文件,所以任何语言(python、go等)只要有合适的解释器解释为.class文件,就可以在java虚拟机上执行。下文为java官方对于Class文件和虚拟机关系之间的描述原文。

    The Java Virtual Machine knows nothing of the Java programming language, only of a particular binary format, the class file format. A class file contains Java Virtual Machine instructions (or bytecodes) and a symbol table, as well as other ancillary information. For the sake of security, the Java Virtual Machine imposes strong syntactic and structural constraints on the code in a class file. However, any language with functionality that can be expressed in terms of a valid class file can be hosted by the Java Virtual Machine. Attracted by a generally available, machine-independent platform, implementors of other languages can turn to the Java Virtual Machine as a delivery vehicle for their languages.

  3. java虚拟机:Java Virtual Machine(缩写为JVM),仅识别.class文件,可以把.class文件加载到内存中,生成对应的java对象。还有内存管理、程序优化、锁管理等功能。所有的java程序最终都运行在jvm之上。下文为java官方对于JAVA虚拟机的描述信息

    The Java Virtual Machine is the cornerstone of the Java platform. It is the component of the technology responsible for its hardware- and operating systemindependence, the small size of its compiled code, and its ability to protect users from malicious programs. The Java Virtual Machine is an abstract computing machine. Like a real computing machine, it has an instruction set and manipulates various memory areas at run time. It is reasonably common to implement a programming language using a virtual machine;

JAVA类加载示例图

idea程序示例

下文将用idea中的java项目示例对Java 源程序、 Java 字节码、类实例分别进行示范:

idea-java源文件

通常来说,我们在idea中写的java程序都属于java源程序,idea会把文件的[.java]后缀隐藏掉。我们也可以使用任何文本编辑器编写生成[.java]文件。下图展示了一个典型的JAVA文件

idea-java源程序示例

idea-java字节码

java文件是不能被java虚拟机所识别的,需要翻译为字节码文件才可以被java虚拟机接受。idea中可以直接点击build项目按钮实现源文件解释为字节码的过程(本质是通过java中的javac工具实现)。

idea字节码展示

idea-类加载

在idea中新建java的主类,并在主类中触发测试类的类加载流程(如new一个测试类),通过断点的方式可以查看到加载好的类的信息。

idea类加载简述

类加载器介绍

类加载器的作用

由上文中的流程图可以看出,类加载器负责读取 Java 字节代码(.class 文件),并转换成 java.lang.Class 类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance() 方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

类加载的时机

java类加载使用动态类加载机制, 程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。JVM运行过程中,首先会加载初始类,然后再从初始类链接触发它相关的类的加载。

类加载的时机

注意:图中的“引用”指触发类加载,一共有以下几种情况会触发类加载:

  1. 创建类的实例 访问类的静态变量(注意:当访问类的静态并且final修饰的变量时,不会触发类的初始化。),或者为静态变量赋值。

  2. 调用类的静态方法(注意:调用静态且final的成员方法时,会触发类的初始化!一定要和静态且final修饰的变量区分开!!)

  3. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。如:Class.forName(“bacejava.Langx”);

  4. 注意通过类名.class得到Class文件对象并不会触发类的加载。 初始化某个类的子类

  5. 直接使用java.exe命令来运行某个主类(java.exe运行,本质上就是调用main方法,所以必须要有main方法才行)。

    java官方对于类加载的描述:The Java Virtual Machine starts up by creating an initial class or interface using the bootstrap class loader or a user-defined class loader . The Java Virtual Machine then links the initial class or interface, initializes it, and invokes the public static method void main(String[]). The invocation of this method drives all further execution. Execution of the Java Virtual Machine instructions constituting the main method may cause linking (and consequently creation) of additional classes and interfaces, as well as invocation of additional methods.
    The initial class or interface is specified in an implementation-dependent manner. For example, the initial class or interface could be provided as a command line argument. Alternatively, the implementation of the Java Virtual Machine could itself provide an initial class that sets up a class loader which in turn loads an application. Other choices of the initial class or interface are possible so long as they are consistent with the specification given in the previous paragraph.

类加载器的意义

类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet 需要从远程下载 Java 类文件到浏览器中并执行。现在类加载器在 Web 容器和 OSGi 中得到了广泛的使用。一般来说,Java 应用的开发人员不需要直接同类加载器进行交互。Java 虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量的时间去调试 ClassNotFoundException 和 NoClassDefFoundError 等异常。

JAVA类加载示例图

类加载的基本流程

JAVA类加载步骤图

1.加载:加载是通过类加载器(classLoader)完成的,它既可以是饿汉式eagerly load加载类(预加载),也可以是懒加载lazy load(运行时加载)

2.验证:确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 验证阶段是否严谨,直接决定了Java虚拟机是否能承受恶意代码的攻击。 从整体上看,验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

3.准备:准备阶段的主要任务是如下两点:为类变量分配内存;设置类变量初始值

4.解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

5.初始化:初始化阶段即虚拟机执行类构造器<clinit>()方法的过程。

6.使用:正常使用类信息

7.卸载:满足类卸载条件时(比较苛刻),jvm会从内存中卸载对应的类信息

oracle官网对于类加载只粗略划分为了三个阶段,加载(包含上图中的加载、验证和准备)、链接和初始化,以下为java官方对于类加载的描述信息

The Java Virtual Machine dynamically loads, links and initializes classes and interfaces. Loading is the process of finding the binary representation of a class or interface type with a particular name and creating a class or interface from that binary representation. Linking is the process of taking a class or interface and combining it into the run-time state of the Java Virtual Machine so that it can be executed. Initialization of a class or interface consists of executing the class or interface initialization method <clinit>

类加载器详细介绍

生成类对象的三种方法

生成类加载器的方法

oracle官网把类加载器划分为两种类型:启动类加载器(BootStrapClassloader)和用户自定义类加载器,用户自定义加载器都继承自ClassLoad类。启动类加载器主要用于加载一些核心java库,如rt.jar。用户自定义加载器则可以加载各种来源的class文件。以下为java官方对于类加载器生成方式的描述信息。
>There are two kinds of class loaders: the bootstrap class loader supplied by the Java Virtual Machine, and user-defined class loaders.Every user-defined class loader is an instance of a subclass of the abstract class ClassLoader. Applications employ user-defined class loaders in order to extend the manner in which the Java Virtual Machine dynamically loads and thereby creates classes. User-defined class loaders can be used to create classes that originate from user-defined sources. For example, a class could be downloaded across a network, generated on the fly, or extracted from an encrypted file.

数组本身也是一个对象,但是这个对象对应的类不通过类加载器加载,而是通过JVM生成。以下为java官方对于数组对象的描述信息
>Array classes do not have an external binary representation; they are created by the Java Virtual Machine rather than by a class loader.

综上所述:类的生成方式一共有三种:

  1. 启动类加载器

  2. 用户自定义类加载器

  3. JVM生成数组对象

    The Java Virtual Machine uses one of three procedures to create class or interface C denoted by N:
    • If N denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C:
    – If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C .
    – If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C.
    • Otherwise N denotes an array class. An array class is created directly by the Java Virtual Machine, not by a class loader. However, the defining class loader of D is used in the process of creating array class C.

启动类加载器

启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
双亲委派模型中,如果一个类加载器的父类加载器为null,则表示该类加载器的父类加载器是启动类加载器

Bootstrap class loader. It is the virtual machine’s built-in class loader, typically represented as null, and does not have a parent.
The following steps are used to load and thereby create the nonarray class or interface C denoted by N using the bootstrap class loader. First, the Java Virtual Machine determines whether the bootstrap class loader has already been recorded as an initiating loader of a class or interface denoted by N. If so, this class or interface is C, and no class creation is necessary. Otherwise, the Java Virtual Machine passes the argument N to an invocation of a method on the bootstrap class loader to search for a purported representation of C in a platform-dependent manner. Typically, a class or interface will be represented using a file in a hierarchical file system, and the name of the class or interface will be encoded in the pathname of the file. Note that there is no guarantee that a purported representation found is valid or is a representation of C. This phase of loading must detect the following error:
• If no purported representation of C is found, loading throws an instance of
ClassNotFoundException.

用户自定义类加载器

用户自定义类加载器可以分为两种类型:

  1. java库中的平台类加载器和应用程序类加载器等
  2. 用户自己写的类加载器,比如通过网络加载类等机制

类加载器的继承结构

数组类加载器

数组的Class类是由jvm生成的,但是数组类的Class.getClassLoader() 和数组元素的类加载器保持一致,如果数组的元素是基本类型,那么数组类的类加载器会为空。

Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.

用户自定义类加载器介绍

本章节会详细介绍下图中的各个类加载器:

类加载器的继承结构

基本类加载器ClassLoader

参考文档:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html

ClassLoader 类是所有类加载器的基类。ClassLoader 类基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外, ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等。不过本节只讨论其加载类的功能。为了完成加载类的这个职责, ClassLoader 提供了一系列的方法,比较重要的方法如 java.lang.ClassLoader 类介绍 所示。关于这些方法的细节会在下面进行介绍。

A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system. Every Class object contains a reference to the ClassLoader that defined it.

ClassLoader默认支持并发加载,可以通过ClassLoader.registerAsParallelCapable方法主动取消并发加载操作,ClassLoader实现并发加载的原理如下:当ClassLoader加载类时,如果该类是第一次加载,则会以该类的完全限定名称作为Key,一个new Object()对象为Value,存入一个ConcurrentHashMap的中。并以该object对象为锁进行同步控制。同一时间如果有其它线程再次请求加载该类时,则取出map中的对象object,发现该对象已被占用,则阻塞。也就是说ClassLoader的并发加载通过一个ConcurrentHashMap实现的。

    // java加载类时获取锁的流程
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-御狐神-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值