Java类加载机制

本文详细介绍了Java的类加载机制,包括加载、Linking(验证、准备、解析)和初始化阶段。类加载器如BootStrapClassLoader、ExtensionClassLoader和SystemClassLoader在其中起到关键作用。双亲委派机制确保了类的安全加载和防止重复加载。同时,自定义类加载器的实现原理也被阐述。
摘要由CSDN通过智能技术生成

Java字节码

Java文件要执行首先需要将Java源代码编译为class文件,也就是字节码文件。字节码文件是一个与平台无关的二进制文件,字节码中的数据内容需要符合JVM的规范才能被正确地解析。例如字节码文件的开头是一个称为魔数的标志位,所有符合JVM规范的class文件都需要以魔数0xcafe babe开头才可以。

Java的类加载机制

JVM将字节码文件加载到内存中并进行初始化操作这个过程被称为类加载过程。类加载过程分为三个步骤,分别是加载、Linking以及Initialization。

加载阶段

加载阶段和类加载过程不要弄混淆,加载阶段只是类加载的一个步骤,主要有以下的特点:

  1. 通过全类名获取指定类的字节码的二进制数据,将这个类的静态的数据结构转换为方法区中运行时的数据结构

  2. 在内存中生成一个这个类对应的java.lang.Class文件,每一个class文件都会生成Class对象,作为方法区的类入口文件

  3. 加载阶段的主要工作是将通过全类名将类的二进制文件加载进内存,为类创建处java.lang.Class对象

Linking阶段

Linking阶段也就是连接阶段,这个阶段也分为三个步骤,分别是验证verify、准备prepare以及解析Resolve。主要工作是对class文件所代表的数据进行验证、对类变量以及将常量池中的符号引用替换为直接引用等。

验证Verify:主要是出于安全考虑,JVM需要对传入的字节码进行校验,是否符合JVM的规范。因为JVM的定义是一种跨语言的平台,不管是何种语言只要生成的字节码符合JVM规范都可以通过JVM来编译执行。而且传入的字节码可以是任何来源,如文件、网络、数据库等。如果对传入的字节码内容不加以任何校验的话,可能就会出现安全风险或者JVM运行被中断等。验证内容主要有文件格式校验、元数据校验、字节码校验、符号引用校验等。
准备Prepare:主要是对类变量分配内存并设置默认值,类变量也就是使用了static修饰符的变量,默认值为变量类型的零值。例如int的零值是0,引用类型的零值是null等。这个过程不会为实例变量设置默认值,而且如果一个类变量同时使用了final关键字进行修饰的话,就是一个常量值,这个常量值的初始化在编译生成class文件时就已经完成。
解析Resolve:此步骤主要的工作是将常量池中的符号引用转换为直接引用。一般这个过程可能会在JVM Initialization完成之后才执行,因为那个时候才存在直接引用。直接引用表示的是直接指向对象的地址,而符号引用表示的只是一个标识。解析阶段可以认为是将常量池的对象真正的分配了一个内存地址。这个内存地址可以真正的访问到的。而不再只是一个符号标识了。
所以Linking阶段的主要工作可以总结为校验class文件、类变量默认值设置、常量池中符号引用替换为直接引用。

初始化阶段

初始化阶段Initialization阶段,主要的工作就是执行类构造器方法,这个类构造器方法并不需要程序员去定义,而是JVM自动收集类中定义的类属性的赋值操作以及static代码块中的语句合并在一起。在这个过程中,static只能对定义在它前面的类变量进行引用和赋值操作,对于定义在它后面的类变量可以赋值,但是不能使用,否则会报前向引用异常。

Java的类加载机制
JVM保证类的父类的会先于当前类的方法执行,所以java.lang.Object会第一个执行,因为它是所有类的父类。

JVM会保证在多线程环境下,类的会被正确地执行,保证在同一时间只有一个线程能进行类的初始化。

类的初始化阶段主要的工作是对类变量进行初始化,调用类初始化方法。

类加载器

类加载过程需要类加载器来实现,在Java中有三种内置的类加载器分别是BootStrap ClassLoader、Extension Classloader以及System Cloassloader(或者是APP Classloader)。除了这三种之外,用户还可以自定义类加载器,用户自定义的类加载器只需要继承ClassLoader类即可,去重写findClass方法或者loadClass均可。

三种内置的类加载器的层级结构如下:

BootStrap ClassLoader <— Extension ClassLoader <— System ClassLoader

这三种加载器本质上并不是继承关系,而是通过组合来实现的一种层级关系。每一种内置加载器大体上的功能如下:

Bootstrap ClassLoader:是最顶层的类加载器,主要用来加载Java的核心类库如JAVA_HOME/jre/lib/rt.jar、resouces.jar等。底层通过C/C++来实现的,是内置在JVM内部的,并没有继承自ClassLoader类。其他两个内置类加载器Extension ClassLoader和System ClassLoader都是通过BootStrap ClassLoader来加载的。形式上作为其他类加载器的顶层的父类。出于安全考虑,BootStrap加载器只加载java、javax、以及sun开头的类,这一点会在双亲委派机制下说明。
Extension ClassLoader:扩展类加载器,它的父类加载器是Bootstrap ClassLoader。扩展类加载器是通过Java程序编写的,继承自ClassLoader类。主要用来加载JAVA_HOME/jre/ext目录下或者java.ext.dris环境变量所指定的目录下的类库,用户如果在这些目录下放置了自己编写的类库也由扩展类加载器进行加载。
System ClassLoader:系统类加载器,也称为了应用类加载器。它的父类加载器是扩展类加载器,系统类加载器通过Java编写,继承自ClassLoader类。是默认的类加载器,Java应用大都是通过它进行加载的,可以用来加载java.class.path环境变量所代表的目录下的类库以及classpath环境变量下的类库。

public class User  {
    public static void main(String[] args) {
        // 通过ClassLoader.getSystemClassLoader可以获取
        // 系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        // String类是java.lang包下的
        // 所以类加载器是BootStrap ClassLoader
        // 是native实现的,所以无法获取到,返回null
        System.out.println(String.class.getClassLoader());

        // 可以看到用户自定义的类的类加载器是Launcher$AppClassLoader
        // 也就是应用加载器,而它的父类加载器是Extension ClassLoader
        // Extension ClassLoader父类加载器是BootStrap ClassLoader
        // 因为是C/C++实现的,所以获取不到,为null
        ClassLoader classLoader = User.class.getClassLoader();
        System.out.println(classLoader);

        System.out.println(classLoader.getParent());
        System.out.println(classLoader.getParent().getParent());
    }
}
双亲委派机制

类加载器进行类加载工作的时候,并不是立刻自己尝试加载指定的类,而是将其委托给父类进行加载,例如系统类加载器加载的时候会将类加载的工作委托给Extension ClassLoader,而Extension ClassLoader又会委托给BootStrap ClassLoader, 因为BootStrap ClassLoader是最顶层的类加载器,所以不会再次向上委派,而是尝试进行加载,如果没有加载成功,子类再尝试加载。自定义的类加载器的加载机制也是如此,当然所有类加载器都没有成功加载的话,会抛出ClassNotFound异常。

双亲委派机制的优点主要有:

出于安全考虑,可以防止java的核心类库被篡改,因为Java程序可以自己定义包以及类,如果用户定义了以java、javax或者sun开头的类库,也不会被加载。因为所有的类的加载首先是通过BootStrap ClassLoader来加载的,BootStrap类加载器会保证其是否是其加载范围以及是否能够正确的加载。通常情况下,如果出现以javja、javax、sun开头的用户自定义类库会报以下错误。

Java的类加载机制
防止类被加载多次,只要类被加载过,就会被缓存起来。所以不会出现多次加载的事情。
自定义类加载器
主要是通过继承ClassLoader类,重写findClass方法来实现。

package com.example.demo;

import lombok.SneakyThrows;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public class CustomClassLoader extends ClassLoader {
    private String path;

    public CustomClassLoader(String path){
        this.path = path;
    }

    @SneakyThrows
    @Override
    protected Class<?> findClass(String className) {
        Class clz = null;

        byte[] data = getClassData();

        if (data != null){
            clz = super.defineClass(className, data, 0, data.length);
        }

        return clz;
    }

    public byte[] getClassData() throws IOException {
        FileInputStream inputStream = new FileInputStream(new File(path));

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        byte[] data = new byte[1024];
        int len = 0;
        while ((len = inputStream.read(data)) != -1){
            outputStream.write(data, 0, len);
        }

        byte[] result = outputStream.toByteArray();

        outputStream.close();
        inputStream.close();

        return result;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        String path = "H:\\code\\java\\demo2\\target\\classes\\com\\example\\demo\\bean\\User.class";
        CustomClassLoader classLoader = new CustomClassLoader(path);

        Class user = classLoader.loadClass("com.example.demo.bean.User");

        System.out.println(user);

        Object ins = user.newInstance();
        System.out.println(user.getDeclaredMethod("test", null).invoke(ins, null));
    }
}
package com.example.demo.bean;

public class User {
    public String test(){
        return "Test ClassLoader";
    }
}

总结:

  1. 类加载的是JVM字节码,字节码可以来自于任何介质,只要符合JVM规范即可。

  2. 类加载的大体过程主要有加载–>Lingking---->初始化Initialization三个阶段。

3.类加载通过类加载器来实现,内置的类加载器有BootStrap ClassLoader,Extension ClassLoader以及System ClassLoader。

4.类加载采用双亲委派机制,可以避免重复加载以及对java的核心库的加载进行覆盖等行为。

5.自定义类加载器通过继承ClassLoader类,重写findClass方法即可。

6.整个类加载过程只有加载阶段可以是程序员可以控制的,其他阶段都是JVM控制和主导的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值