JVM组成之类加载器

news2024/11/16 2:23:50

类加载器(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.coloradmin.cn/o/1644125.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2010NOIP普及组真题 2. 接水问题

线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1950 解法一、朴素模拟 核心思想&#xff1a; 朴素模拟&#xff1a; 1、先给每个b[i]水龙头分配一个人a[i]&#xff0c;b[i] 表示水龙头的剩余时间。同时标记该水龙头为 used 使用中 2…

(论文阅读-优化器)A Cost Model for SPARK SQL

目录 Abstract 1 Introduction 2 Related Work 3 Background and Spark Basics 4 Cost Model Basic Bricks 4.1 Cluster Abastraction and Cost Model Parameters 4.2 Read 4.3 Write 4.4 Shuffle Read 4.5 Broadcast 5 Modeling GPSJ Queries 5.1 Statistics and S…

交互中的“互”难以产生的原因

脑机交互技术的目标是通过分析和解读大脑活动&#xff0c;将其与特定的意图、指令或行为连接起来。通过训练和分析&#xff0c;可以建立起大脑活动与特定行为或意图之间的关联模型&#xff0c;从而实现脑机交互的应用&#xff0c;例如控制外部设备、传递信息等。然而&#xff0…

视频教程下载:为 GPTs 商店构建 10 个 GPTs获得被动收入

欢迎来到 AI 驱动的内容创作新时代 - GPT 商店。这门综合课程是您成为定制和利用 GPT 模型解决多样化应用的专家的路线图。无论你是错过了应用商店革命的初始浪潮还是乘着它取得了成功&#xff0c;这都是你站在下一个重大数字飞跃前沿的机会。 课程模块&#xff1a; - 介绍 Ch…

抓包证书安装到安卓7.0+手机

前言: 首先理解一下,这个不只是证书到浏览器,而是抓包证书到安卓7.0+手机上的文章; 还有一点区分,在浏览器上装的证书,只是让抓包工具可以抓取手机浏览器的包,而不是抓取手机app上的包; 如果你的证书只是简单的在浏览器下进行安装,那么你的手机app是走不了代理网络的…

iOS - Undefined symbols: 解决方法

Undefined symbols: 是让人苦恼的报错&#xff0c;如何知道是 哪个 symbols 不对呢&#xff1f; 今天探索到下面的方法&#xff1a; 1、点击导航上方 最右侧的按钮&#xff0c;查看历史报错 2、选中报错信息&#xff0c;右键选择 Expand All Transcripts 在出现的详细信息面…

【Redis】Redis命令(一)

1.基本命令 1.1.切换DB 默认使用的是 0 号 DB&#xff0c;可以通过 select db 索引来切换 DB 1.2.查看 key 数量 dbsize 命令可以查看当前数据库中 key 的数量 1.3.删除当前库中数据 flushdb 命令仅仅删除的是当前数据库中的数据&#xff0c;不影响其它库 1.4.删除所有库中数据…

Spring Cloud架构进化实操:Eureka、Apollo、OpenFeign、Ribbon、Zuul组件

文章目录 前言一、引出二、服务注册与发现2.1 创建Eureka注册中心2.1.1 引入pom依赖2.1.2 配置yaml2.1.3 启动服务21.4 测试访问 2.2 创建服务提供者2.2.1 配置yaml2.2.2 启动服务2.2.3 测试访问 2.3 创建服务消费者2.3.1 服务提供者接口2.3.2 服务消费者调用接口 三、负载均衡…

如何高速下载,百度 阿里 天翼 等网盘内的内容

如何高速下载&#xff0c;百度 阿里 天翼 等网盘内的内容&#x1f3c5; 前言教程下期更新预报&#x1f3c5; 前言 近段时间经常给大家分享各种视频教程&#xff0c;由于分享的资料是用迅雷网盘存的&#xff0c;但是绝大部分用户都是使用的某度&#xff0c;阿某的这些网盘&…

OpenCV如何使用 GDAL 读取地理空间栅格文件(72)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV的周期性噪声去除滤波器(70) 下一篇 :OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 目录 目标 代码&#xff1a; 解释&#xff1a; 如何使用 GDAL 读取栅格数据 注意 …

DS:顺序表、单链表的相关OJ题训练(1)

欢迎各位来到 Harper.Lee 的学习小世界&#xff01; 博主主页传送门&#xff1a;Harper.Lee的博客主页 想要一起进步的uu可以来后台找我交流哦&#xff01; 在DS&#xff1a;单链表的实现 和 DS&#xff1a;顺序表的实现这两篇文章中&#xff0c;我详细介绍了顺序表和单链表的…

【区块链】比特币架构

比特币架构 2009年1月&#xff0c;在比特币系统论文发表两个月之后&#xff0c;比特币系统正式运行并开放了源码&#xff0c;标志着比特币网络的正式诞生。通过其构建的一个公开透明、去中心化、防篡改的账本系统&#xff0c;比特币开展了一场规模空前的加密数字货币体验。在区…

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…

Unity之ShaderGraph入门简介与配置

前言 ShaderGraph是Unity的一个可视化着色器编辑工具,它允许开发者在不编写代码的情况下创建复杂的着色器效果。ShaderGraph提供了一个直观的图形界面,用户可以通过拖拽节点并连接它们来构建自定义的着色器。用户可以在ShaderGraph中使用各种节点,如数学运算、纹理采样、颜…

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(五)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 继续接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 在下面的图片中&#…

「 网络安全常用术语解读 」通用配置枚举CCE详解

1. 背景介绍 NIST提供了安全内容自动化协议&#xff08;Security Content Automation Protocol&#xff0c;SCAP&#xff09;为漏洞描述和评估提供一种通用语言。SCAP组件包括&#xff1a; 通用漏洞披露(Common Vulnerabilities and Exposures, CVE)&#xff1a;提供一个描述…

K8S 哲学 - 服务发现 services

apiVersion: v1 kind: Service metadata:name: deploy-servicelabels:app: deploy-service spec: ports: - port: 80targetPort: 80name: deploy-service-podselector: app: deploy-podtype: NodePort service 的 endPoint &#xff08;ep&#xff09; 主机端口分配方式 两…

LeetCode 234.回文链表

题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为 回文链表 。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff…

学习笔记:【QC】Android Q - IMS 模块

一、IMS init 流程图 二、IMS turnon 流程图 三、分析说明 1、nv702870 不创建ims apn pdp 2、nv702811 nv702811的时候才创建ims pdp&#xff1a; ims pdp 由ims库发起&#xff0c;高通没有开放这部分代码&#xff1a; 10-10 11:45:53.027 943 943 E Diag_Lib: [IMS_D…

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

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