JVM组成之类加载器

类加载器(ClassLoader):是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。

 类加载器多数是有Java编写的,也有部分是c++编写的,负责接收来自外部的二进制数据,然后执行JNI(也就是本地接口调用)。   JNI:是Java Native Interface的缩写,运行Java调用其他语言编写的方法。

 类加载器分为俩类:一类是Java虚拟机底层源码实现,一类是Java代码中实现的(即自定义的classLoader)

  虚拟机底层实现:用于加载程序运行时的基础类,保证Java程序运行中基础类被正确加载。

    jdk8及以前:有启动类加载器bootstrap(c++编写):加载Java中最核心的类。Java代码实现继承自抽象类ClassLoader。所有的类加载器都需要继承这个类。

package org.example.classLoader;

import java.io.IOException;

public class BootstrapClassLoader {
    public static void main(String[] args) throws IOException {
        ClassLoader classLoader=String.class.getClassLoader();
        System.out.println(classLoader);

        System.in.read();
    }
}

打印结果为null。

启动类加载器存在于虚拟机中,属于底层。而通过Java代码去获取这个类加载器属于高层,虚拟机会阻止通过代码去获取到启动类加载器并操作的,所有通过代码去获取启动类加载器返回的结果是null。

 arthas中有命令:sc -d 类名   就是查看该类的类加载器。

  通过启动类去加载用户的jar包的方法:

  1、不推荐:将用户的jar包放入jre/lib下进行扩展。尽可能不要更改jdk安装目录的内容。

  2、使用参数扩展:使用-Xbootclasspath/a:jar包目录/jar包进行扩展。

 扩展类加载器extension:运行扩展Java中比较通用的类。

package org.example.classLoader;

import jdk.nashorn.internal.runtime.ScriptEnvironment;

public class ExtClassLoader {
    public static void main(String[] args) throws ClassNotFoundException {
        
        //扩展类加载器
        ClassLoader classLoader1= ScriptEnvironment.class.getClassLoader();
        System.out.println(classLoader1);
    }
}

应用类加载器application:加载应用使用的类。

实体类:student

package org.example.pojo;

import lombok.Builder;

@Builder
public class Student {
    public String name;
    public int age;

    public static void main(String[] args) {
        Student student=Student.builder().name("张三").age(18).build();
    }
}
package org.example.classLoader;

import org.example.pojo.Student;
import org.apache.commons.io.FileUtils;

import java.io.IOException;


public class AppClassLoader {
    public static void main(String[] args) throws IOException {
         //Student student = new Student();
        ClassLoader classLoader=Student.class.getClassLoader();
        System.out.println(classLoader);

        //maven依赖中包含的类
        ClassLoader classLoader1= FileUtils.class.getClassLoader();
        System.out.println(classLoader1);

        System.in.read();
    }
}

扩展类加载器和应用类加载器都是jdk中提供的、使用Java编写的,它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录或者指定jar包将字节码文件加载到内存中。

 通过扩展类加载器去加载用户jar包方法:

 1、放入\jre\lib\ext下进行扩展(不推荐)。

 2、使用-Djava.ext.dirs=jar包目录进行扩展,这种方式会覆盖掉原始目录。想扩展多个jar包目录时,多个jar包目录Windows用;  mac用: 连接。

类加载器的双亲委派机制:

内容:当一个类加载器接收到加载类的任务时,会自下而上查找是否被加载过,在由顶向下进行加载(根据类的加载路径选择加载到哪个类加载器中)。

 由于Java虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底是谁加载的问题。

  作用:

 1、保证类加载的安全性:避免代码替换jdk中的核心类库,确保核心类库完整性和安全性。

 2、避免重复加载:双亲委派机制可以避免同一个类被多次加载。

多个类加载器之间有层次变化

 虽然类加载器之间是父类加载器关系,但他们之间并不是继承关系而是上下级关系。 自定义加载器父类为应用类加载器。应用类加载器父类为扩展类加载器。虽然扩展类加载器的parent=null,但通过复杂的底层逻辑实现,我们可以认为扩展类加载器的父类是启动类加载器。

 双亲委派机制问题:

  如果一个类重复出现在三个类加载器的加载位置,英国由启动类加载器加载,根据双亲委派机制,它的优先级最高。

  在自己的项目中创建一个java.lang.string类(在启动类加载器中已有相同类名的类被加载),不会被加载,会返回启动类中已被加载的string类。

 

Java中如何使用代码去主动加载一个类:

 1、使用Class.forName方法,使用当前类的类加载器去加载指定的类

 2、获取到类加载器,通过类加载器的loadClass方法指定某个类加载器的加载。

打破双亲委派机制的三种方式:

 1、自定义类加载器:自定义类加载器并重写loadClass方法,就可以将双亲委派机制的代码去除。如果自定义类加载器未指定父加载器是谁,就默认指向应用类加载器。即使俩个自定义类加载器加载相同限定名的类,也不会冲突。同一个Java虚拟机中只有相同类加载器加载相同的类限定名才会被认为是同一个类。

package org.example.classLoader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

//打破双亲委派机制
public class BreakClassLoader1 extends ClassLoader{

    private String basePath;
    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }
    private byte[] loadClassData(String name){
        // 将包名中的"."替换成文件系统的路径分隔符"/"
        // 假设类文件存放在某个固定路径下,这里是"classes"目录
        String filePath = "/Users/quwenhao/Desktop/text1.class";
        File classFile = new File(filePath);

        long len = classFile.length();
        byte[] raw = new byte[(int) len];

        try (FileInputStream fis = new FileInputStream(classFile)) {
            // 读取类文件的字节码
            int r = fis.read(raw);
            if (r != len) {
                throw new IOException("无法读取全部的类文件:" + name);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return raw;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.startsWith("java.")) {
            return super.loadClass(name);
        }

        byte[] data=loadClassData(name);
        return defineClass(name,data,0,data.length);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
        //创建自定义类加载器
        BreakClassLoader1 classLoader1=new BreakClassLoader1();
        //设置类加载器的加载目录
        classLoader1.setBasePath("/Users/quwenhao/Desktop/");
        //加载java.lang.String类。
        //Class<?> loadClass = classLoader1.loadClass("java.lang.String");
        //loadClass.newInstance();

        Class<?> loadClass = classLoader1.loadClass("org.example.text1");
        System.out.println(loadClass.getClassLoader());

        //加载自定义的父类加载器
        ClassLoader parent = classLoader1.getParent();
        System.out.println(parent);
        //获取系统类加载器
        System.out.println(getSystemClassLoader());

        //俩个自定义类加载器加载限定名相同的类,不冲突。同一个虚拟机中,只有同一个自定义类加载器加载相同限定名的类时才会冲突。
        BreakClassLoader1 classLoader2=new BreakClassLoader1();
        Class<?> loadClass1 = classLoader2.loadClass("org.example.text1");
        System.out.println(loadClass==loadClass1);

        Thread.currentThread().setContextClassLoader(classLoader1);
        System.out.println(Thread.currentThread().getContextClassLoader());

        System.in.read();
    }
}

2、线程上下文类加载器:利用上下文类加载器,比如JDBC和JNDI。

3、Osgi(了解即可):历史上Osgi框架实现了一套新的加载器机制,允许同级之间委托进行类的加载。

双亲委派机制的核心代码在Classloader在的4个核心方法之一的loadClass方法中。

 打破双亲委派机制的情况:

 1、当tomcat中运行俩个限定名相同的类,tomcat需要保证这俩个类都能被加载并且是不同的类。这时就需要打破双亲委派机制,tomcat使用类自定义类加载器来实现应用之间的隔离。每一个应用会有一个独立的类加载器加载对应的类。

package org.example.classLoader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

//打破双亲委派机制
public class BreakClassLoader1 extends ClassLoader{

    private String basePath;
    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }
    private byte[] loadClassData(String name){
        // 将包名中的"."替换成文件系统的路径分隔符"/"
        // 假设类文件存放在某个固定路径下,这里是"classes"目录
        String filePath = "/Users/quwenhao/Desktop/text1.class";
        File classFile = new File(filePath);

        long len = classFile.length();
        byte[] raw = new byte[(int) len];

        try (FileInputStream fis = new FileInputStream(classFile)) {
            // 读取类文件的字节码
            int r = fis.read(raw);
            if (r != len) {
                throw new IOException("无法读取全部的类文件:" + name);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return raw;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.startsWith("java.")) {
            return super.loadClass(name);
        }

        byte[] data=loadClassData(name);
        return defineClass(name,data,0,data.length);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
        //创建自定义类加载器
        BreakClassLoader1 classLoader1=new BreakClassLoader1();
        //设置类加载器的加载目录
        classLoader1.setBasePath("/Users/quwenhao/Desktop/");
        //加载java.lang.String类。
        //Class<?> loadClass = classLoader1.loadClass("java.lang.String");
        //loadClass.newInstance();

        Class<?> loadClass = classLoader1.loadClass("org.example.text1");
        System.out.println(loadClass.getClassLoader());

        //加载自定义的父类加载器
        ClassLoader parent = classLoader1.getParent();
        System.out.println(parent);
        //获取系统类加载器
        System.out.println(getSystemClassLoader());

        //俩个自定义类加载器加载限定名相同的类,不冲突。同一个虚拟机中,只有同一个自定义类加载器加载相同限定名的类时才会冲突。
        BreakClassLoader1 classLoader2=new BreakClassLoader1();
        Class<?> loadClass1 = classLoader2.loadClass("org.example.text1");
        System.out.println(loadClass==loadClass1);
    }
}

   2、JDBC案例

Driver Manager属于rt.jar是启动类加载器加载的,而用户jar包的驱动器需要有应用类加载器加载,这就违反了双亲委派机制。

其实JDBC案例可以说打破了,也可以说没有打破。打破了:是启动类加载器委派应用类加载器去加载类跳过了扩展类加载器。没有打破:触发驱动类的加载,驱动类的加载还是自顶向下加载,遵循双亲委派机制。

DriverManager是通过spi机制知道jar包中要加载的驱动在哪的。spi机制是jdk内置的一种服务发现机制。

 spi机制:1、在ClassPath路径下的META-INF/services文件夹中,以接口的全限定名来命名文件名,对应文件里面写该接口的实现。2、使用serviceLoader加载实现类。

 spi中使用了上下文中保存的类加载器进行类的加载,这个类一般就是应用类加载器,由此spi可以获取到应用类加载器。获取到了线程上下文类加载器,启动类加载器会委托上下文类加载器去驱动类。

package org.example.classLoader;

import com.sun.xml.internal.bind.v2.model.annotation.RuntimeAnnotationReader;

public class NewThreadDemo {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //当线程被创建后,线程中的上下文类加载器就已经是应用类加载器了。
                System.out.println(Thread.currentThread().getContextClassLoader());
            }
        }).start();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/594132.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java】山外有山,类外还有类

【Java】山外有山&#xff0c;类外还有类 内部类是Java语言中的一种特性&#xff0c;它允许在另一个类中定义一个类。 内部类可以是静态的&#xff08;不依赖于外部类的实例&#xff09;&#xff0c;也可以是非静态的&#xff08;依赖于外部类的实例&#xff09;。 在本篇博…

在R的 RGui中,使用devtools 安装trajeR

创建于&#xff1a;2024.5.5 文章目录 1. 报错信息2. 尝试使用指定的清华镜像&#xff0c;没有解决3. 找到原因&#xff1a;官网把包删除了4. 尝试从网上下载&#xff0c;然后安装。没有成功5. 使用devtools安装5.1 尝试直接安装&#xff1a;install.packages("devtools&q…

【智能算法应用】混合粒子群算法求解CVRP问题

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】粒子群算法&#xff08;PSO&#xff09;原理及实现 经典PSO算法用于连续空间优化问题&#xff0c;VRP问题为离散组合优化问题&#xff0c;涉及如何有效地分配一组车辆去访问多个客户点&…

OSEK的设计哲学与架构

1 前言 OSEK是为单核分布式嵌入式控制单元量身定制的实时系统&#xff0c;对事件驱动&#xff08;event driven&#xff09;的硬实时控制系统具有良好的适配性。OSEK没有强求不同软件模块间的完全兼容性&#xff0c;而是将重心放到了软件的可移植性上来。简单来说&#xff0c;与…

[报错解决]Communications link failure

报错 主机IDEA项目连接虚拟机的数据库报错。 主要报错信息有&#xff1a; com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received a…

智慧旅游引领未来风尚,科技助力旅行更精彩:科技的力量推动旅游业创新发展,为旅行者带来更加便捷、高效和智能的旅行服务

目录 一、引言 二、智慧旅游的概念与特点 &#xff08;一&#xff09;智慧旅游的概念 &#xff08;二&#xff09;智慧旅游的特点 三、科技推动旅游业创新发展 &#xff08;一&#xff09;大数据技术的应用 &#xff08;二&#xff09;人工智能技术的应用 &#xff08;…

Linux Ubuntu 开机自启动浏览器

终端输入命令&#xff1a;gnome-session-properties 打开启动设置 如果提示&#xff1a;Command ‘gnome-session-properties’ not found, but can be installed with: apt install gnome-startup-applications 则执行&#xff1a;apt install gnome-startup-applications安装…

一、写给Android开发者之harmony入门

一、创建新项目 对比 android-studio&#xff1a;ability类似安卓activity ability分为两种类型(Stage模型) UIAbility和Extensionability&#xff08;提供系统服务和后台任务&#xff09; 启动模式 1、 singleton启动模式&#xff1a;单例 2、 multiton启动模式&#xff1…

数据结构十:哈希表

本次将从概念上理解什么是哈希表&#xff0c;理论知识较多&#xff0c;满满干货&#xff0c;这也是面试笔试的一个重点区域。 目录 一、什么是哈希表 1.0 为什么会有哈希表&#xff1f; 1.1 哈希表的基本概念 1.2 基本思想 1.3 举例理解 1.4 存在的问题 1.5 总结 二、…

libcity笔记:参数设置与参数优先级

1 参数优先级 高优先级的参数会覆盖低优先级的同名参数 Libcity中的优先级顺序维&#xff1a; 命令行参数&#xff08;命令行python run_model.py时导入的&#xff09; > 用户定义配置文件&#xff08;命令行python run_model.py时由config_file导入的&#xff09; >…

javascript 练习 写一个简单 另类录入 电脑组装报价表 可打印

数据格式 &#xff08;1代表cpu、2代表主板、3代表内存、。。。&#xff09; 1i3 12100 630 2H610 480 3DDR4 3200 16G 220 4500G M.2 299 5300W电源 150 6小机箱 85 7GT 730G 4G 350 8WD 2T 399 9飞利浦 24Led 580 主代码 Html JS <!DOCTYPE html> <html lang&qu…

02_Java综述

目录 面向对象编程两种范式抽象OOP 三原则封装继承多态多态、封装与继承协同工作 面向对象编程 面向对象编程(Object-Oriented Programming&#xff0c;OOP)在Java中核心地位。几乎所有的Java程序至少在某种程度上都是面向对象的。OOP与java是密不可分的。下面说一下OOP的理论…

SSM+Vue酒店管理系统

SSMVue酒店管理系统&#xff0c;JavaWeb酒店管理系统&#xff0c;项目由maven工具管理依赖&#xff0c;数据库Mysql&#xff0c;一共19张表&#xff0c;前端用Vue写的管理端&#xff0c;功能丰富&#xff0c;需要可在最后位置联系我&#xff0c;可加购调试&#xff0c;讲解&…

自注意力架构大成者_Transformer(Pytorch 17)

1 模型简介 在上节比较了 卷积神经网络&#xff08;CNN&#xff09;、循环神经网络&#xff08;RNN&#xff09;和 自注意力&#xff08;self‐attention&#xff09;。值得注意的是&#xff0c; 自注意力同时具有并行计算和最短的最大路径长度这两个优势。因此&#xff0c;使…

Llama3本地部署与高效微调入门

前言 为了保持公司在AI&#xff08;人工智能&#xff09;开源大模型领域的地位&#xff0c;社交巨头Meta推出了旗下最新开源模型。当地时间4月18日&#xff0c;Meta在官网上宣布公布了旗下最新大模型Llama 3。目前&#xff0c;Llama 3已经开放了80亿&#xff08;8B&#xff09…

8086 汇编学习 Part 9

端口的读写 CPU 的邻居 CPU 内部的寄存器内存单元端口&#xff08;各种接口卡、网卡&#xff0c;显卡&#xff0c;主板上的接口芯片等&#xff09; 各种芯片工作时&#xff0c;都有一些寄存器由 CPU 读写从 CPU 角度&#xff0c;将各寄存器当端口&#xff0c;并统一编制CPU …

开源im即时通讯app源码系统/php即时聊天im源码/php+uniapp框架【终身使用】

摘要 随着开源文化的蓬勃发展&#xff0c;即时通讯(IM)系统作为现代通信不可或缺的一部分&#xff0c;其开源实现正变得越来越普遍。本文将深入探讨基于PHP的全开源即时通讯源码系统&#xff0c;并结合UniApp开源框架&#xff0c;从理论基础到代码实现&#xff0c;再到实际应用…

探索设计模式的魅力:分布式模式让业务更高效、更安全、更稳定

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索分布式模式之旅✨ 在数字化时代&#xff0c;企业面临着前所未有的挑战和机遇。…

机器学习-K近邻算法(KNN)

目录 什么是KNN算法 图解KNN基本算法 &#xff08;1&#xff09;k近邻算法中k的选取 &#xff08;2&#xff09;距离函数 &#xff08;3&#xff09;归一化处理 &#xff08;4&#xff09;概率kNN KNN算法的优缺点 优势 缺点 KNN算法总结 什么是KNN算法 k近邻算法&…

vs 2022 Xamarin 生成 Android apk

再保存&#xff0c;如果没有生成apk就重启软件 再试一次