深入理解JVM虚拟机第八篇:引导类加载器 、拓展类加载器、应用类加载器与自定义类加载器

本文详细探讨了JVM中的方法调用机制,包括静态链接与动态链接,早期绑定和晚期绑定的概念及示例。同时,介绍了类加载器的工作原理,如引导类加载器、拓展类加载器、应用类加载器和自定义类加载器的实现与作用。通过理解这些机制,有助于深入掌握Java的运行机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

😉😉 学习交流群:

✅✅1:这是孙哥suns给大家的福利!

✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料

🥭🥭3:QQ群:583783824   📚📚  工作微信:BigTreeJava 拉你进微信群,免费领取!

🍎🍎4:本文章内容出自上述:Spring应用课程!💞💞

💞💞5:以上内容,进群免费领取呦~ 💞💞💞💞

文章目录

一:方法的调用

1:概述

2:静态链接

3:动态链接

二:方法的绑定

1:绑定概念

2:早期绑定

3:晚期绑定

三:晚期绑定示例

1:编写代码

2:jclasslib查看内容

四:早期绑定示例 

1:编写代码

2:jclasslib查看内容

五:总结说明


一:方法的调用

        我们每天都在写方法的调用,但是我们能搞明白其中的原理和JVM当中的操作步骤么?这就是本文的意义。

1:概述

        官方说法:

        在JVM中,将符号引用转换为调用方法的直接引用这个操作是跟JVM当中方法的绑定机制息息相关的。

        说人话:

        上边这段话是什么意思?我这里给大家解释一下,我们javap整理完毕字节码文件之后,我们会可以在任意一个方法中查看code下的字节码指令,很多字节码指令的后边都会跟#数字这么一个概念,这个就是符号引用,这个引用指向常量池。

        所谓将符号引用转换为方法的直接引用,就是将这个字节码指令后边的符号引用,转变为真实的方法。

        下列中的#3就是符号引用。

  public void methodB();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String methodB().....
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: aload_0
         9: invokevirtual #7                  // Method methodA:()V
        12: aload_0
        13: dup
        14: getfield      #2                  // Field num:I
        17: iconst_1
        18: iadd
        19: putfield      #2                  // Field num:I
        22: return

        从上述找一个例子的话,就是将偏移地址为9的字节码指令后边的#7这个符号引用用真实的方法字面量代替

2:静态链接

        官方说法:

        当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。

        说人话:

        静态链接:这种方式在编译阶段就已经把符号引用直接转换为了直接引用。

3:动态链接

        官方说法:

        如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。

        说人话:

        动态链接:这种方式在运行阶段才能把符号引用直接转换为直接引用。

二:方法的绑定

1:绑定概念

        绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。这个不论是编译器确定还是运行期确定都只会发生一次,不会修改。

        对应的方法的绑定机制为:早期绑定 (Early Bindng)和晚期绑定(Late Binding)。

2:早期绑定

        官方说法:

        早期绑定就是指被调用的目标方法如果在编译期可知,且运行期保持不变时即可将这个方法与所属的类型进行绑定,这样一来,由于明确了被调用的目标方法究竟是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用。

        说人话:

        早期绑定是和我们的静态绑定相对应的。

3:晚期绑定

        官方说法:

        如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际的类型绑定相关的方法,这种绑定方式也就被称之为晚期绑定

        说人话:

        晚期绑定是和我们的动态绑定相对应的。

三:晚期绑定示例

1:编写代码

class Animal {
    public void eat(){
        System.out.println("动物进食");
    }
}

interface Huntable{
    void hunt();
}

class Dog extends Animal implements Huntable{
    @Override
    public void eat(){
        System.out.println("狗吃骨头");
    }

    @Override
    public void hunt() {
        System.out.println("捕食耗子,多管闲事");
    }
}

class Cat extends Animal implements Huntable{
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }

    @Override
    public void hunt() {
        System.out.println("捕食耗子,天经地义");
    }
}

public class AnimalTest{
    public void showAnimal(Animal animal){
        animal.eat();//晚期绑定
    }

    public void showHunt(Huntable h){
        h.hunt();//晚期绑定
    }

}

2:jclasslib查看内容

四:早期绑定示例 

1:编写代码

class Animal {
    public void eat(){
        System.out.println("动物进食");
    }
}

interface Huntable{
    void hunt();
}

class Dog extends Animal implements Huntable{
    @Override
    public void eat(){
        super.eat();//早期绑定
        System.out.println("狗吃骨头");
    }

    @Override
    public void hunt() {
        System.out.println("捕食耗子,多管闲事");
    }
}

class Cat extends Animal implements Huntable{
    public Cat(){
        super();//早期绑定
    }
    public Cat(String name){
        this();//早期绑定
    }
    
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }

    @Override
    public void hunt() {
        System.out.println("捕食耗子,天经地义");
    }
}

public class AnimalTest{
    public void showAnimal(Animal animal){
        animal.eat();//晚期绑定
    }

    public void showHunt(Huntable h){
        h.hunt();//晚期绑定
    }

}

2:jclasslib查看内容

        光标放到cat这个类上查看他的jclasslib

         invokeSpecial是早期绑定字节码指令,invokevirtual是晚期绑定的字节码指令。

五:总结说明

        随着高级语言的横空出世,类似于Java一样的基于面向对象的编程语言如今越来越多,尽管这类编程语言在语法风格上存在一定的差别,但是它们彼此之间始终保持着一个共性,那就是都支持封装、继承和多态等面向对象特性

        既然这一类的编程语言具备多态特性,那么自然也就具备早期绑定和晚期绑定两种绑定方式。

        Java中任何一个普通的方法其实都具备虚函数的特征,也就是运行期才能确定下来,它们相当于c++语言中的虚函数 (c++中则需要使用关键字virtual来显式定义)。

        如果在Java程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字final来标记这个方法。也就是一个方法不想被晚期绑定,直接把他给final修饰即可。

文章目录

一:类加载器详细介绍

1:类加载器分类

2:启动类加载器

3:拓展类加载器

4:应用程序类加载器(系统类加载器AppClassLoader)

二:用户自定义类加载器

1:用户自定义类加载器

2:为什么要自定义类加载器?

3:用户自定义类加载器实现步骤

三:ClassLoader常用方法和获取方法

1:常用方法

2:获取方法


一:类加载器详细介绍

1:类加载器分类

        JVM支持两种类型的类加载器,分别为引导类加载器 (BootstrapclassLoader) 和自定义类加载器 (User-Defined classLoader)

        从概念上来讲自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类classLoader的类加载器都划分为自定义类加载器。

        无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示:

        将此图进行对照的话,BootStrap Class Loader加载器属于引导类加载器,剩下的都是自定义类加载器,都继承了ClassLoader这个抽象类。

        拓展类加载器和系统类加载器都属于自定义类加载器范畴。sun.misc.Launcher 是JVM虚拟机的一个入口应用。拓展类加载器和系统级累计加载器是他的静态内部类,他们都继承了ClassLoader

        值得注意的是引导类加载器是使用C和C++编写的,自定义类加载器都是使用Java语言编写的,他们都有一个抽象父类Class Loader。

        并且他们的关系是这样的,下边的箭头不代表继承关系,而是一种等级制度。

2:启动类加载器

        这个类加载使用C/C++语言实现的,嵌套在JVM内部,是JVM自带的类加载器。

        它用来加载Java的核心类库 (JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类

        并不继承自java.lang.ClassLoader,没有父加载器

        加载扩展类和应用程序类加载器,并指定为他们的父类加载器

        出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类

public class ClassLoaderTest1 {
    public static void main(String[] args) {
        System.out.println("**********启动类加载器**************");
        //获取BootstrapCLassLoader能够加载的api的路径
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (URL element : urLs) {
            System.out.println(element.toExternalForm());
        }
    }
}
**********启动类加载器**************
file:/D:/soft/jdk/jre/lib/resources.jar
file:/D:/soft/jdk/jre/lib/rt.jar
file:/D:/soft/jdk/jre/lib/sunrsasign.jar
file:/D:/soft/jdk/jre/lib/jsse.jar
file:/D:/soft/jdk/jre/lib/jce.jar
file:/D:/soft/jdk/jre/lib/charsets.jar
file:/D:/soft/jdk/jre/lib/jfr.jar
file:/D:/soft/jdk/jre/classes

Process finished with exit code 0

        ClassLoader classLoader = com.sun.net.ssl.internal.ssl.Provider.class.getClassLoader();
        System.out.println("classLoader = " + classLoader);//classLoader = null

         ClassLoader是null,说明JSSE包下的这个类,的类加载器是引导类加载器。

3:拓展类加载器

        扩展类加载器 (ExtensiplClassLoader)

         Java语言编写,由sun.misc.LauncherSExtClassLoader实现

        派生于classLoader类

        父类加载器为启动类加载器

        从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录 (扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。也就是说这个类加载器加载的内容看的是目录,它主要加载的就是核心类之外的拓展类。

        System.out.println("**********拓展类加载器**************");
        String property = System.getProperty("java.ext.dirs");
        String[] split = property.split(";");
        for (String s : split) {
            System.out.println(s);
        }
        //D:\soft\jdk\jre\lib\ext
        //C:\WINDOWS\Sun\Java\lib\ext
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@2626b418

4:应用程序类加载器(系统类加载器AppClassLoader)

        java语言编写由sun.misc.Launcher$AppclassLoader实现,$代表是一个内部类。

        派生于classLoader类

        父类加载器为扩展类加载器

        它负责加载环境变量classpath或系统属性 java.class.path 指定路径下的类库

        该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载

        通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器

二:用户自定义类加载器

1:用户自定义类加载器

        在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。

2:为什么要自定义类加载器?

        隔离加载类

        修改类加载的方式

        扩展加载源

        防止源码泄漏

3:用户自定义类加载器实现步骤

        开发人员可以通过继承抽象类java.lang.classLoader类的方式,实现自己的类加载器,以满足一些特殊的需求

        在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadclass()方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadclass()方法,而是建议把自定义的类加载逻辑写在findclass()方法中

        在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URIClassLoader类,这样就可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

三:ClassLoader常用方法和获取方法

        ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader (不包括启动类加载器)

1:常用方法

方法名称描述
getParent()   返回该类加载器的超类加载器
loadClass(String name)加载名称为name的类,返回结果为java.lang.Class类的实例
findClass(String name)查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例
findLoadedClass(String name)查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例
defineClass(String name,byte[] b.int off.int len)把字节数组b中的内容转换为一个Java类,返回结果为java.langClass类的实例
resolveClass(Class<?>c)连接指定的一个Java类

        这里边loadClass与findClass()和defineClass()联合使用的结果是一样的。

        sun.misc.Launcher它是一个java虚拟机的入口应用。ExtClassLoader和AppClassLoader都在Launcher这个类中。作为他的内部类。

2:获取方法

方式一:获取当前类的ClassLoader
clazz.getClassLoader();

方式二:获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()

方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader()

方式四:获取调用者的ClassLoader
DriverManager.getCallerClassLoader()

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

岁岁种桃花儿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值