Java 入门指南:JVM(Java虚拟机)—— Java 类加载器详解

news2024/9/22 21:51:58

类加载器

类加载器(Class Loader)是 Java 虚拟机(JVM)的一部分,它的作用是将类的字节码文件(.class 文件)从磁盘或其他来源加载到 JVM 中。类加载器负责查找和加载类的字节码文件,并将其转化为 Class 对象。

类加载器从 JDK 1.0 就出现了,最初只是为了满足 Java Applet(已经被淘汰) 的需要。后来,慢慢成为 Java 程序中的一个重要组成部分,赋予了 Java 类可以被动态加载到 JVM 中并执行的能力。

根据官方 API 文档的介绍:

类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

每个 Java 类都有一个引用指向加载它的 ClassLoader。但数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

  • 类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。

  • 每个 Java 类都有一个引用指向加载它的 ClassLoader

  • 数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。

组成部分

在 Java 中,类加载器主要有三个层次:

  1. 启动类加载器(Bootstrap ClassLoader):这是最基础的类加载器,由 C++ 实现,通常表示为 null,并且没有父级,负责加载扩展目录下的 jar 包和系统类路径下的核心库( %JAVA_HOME%/lib 目录下的 rt.jarresources.jarcharsets.jar 等 jar 包和类)以及被 -Xbootclasspath 参数指定的路径下的所有类。

    rt.jar:rt 代表“RunTime”,rt.jar 是 Java 基础类库,包含 Java doc 里面看到的所有的类的类文件。也就是说,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*java.io.*java.nio.*java.lang.*java.sql.*java.math.*

  2. 扩展类加载器(Extension ClassLoader):由 Java 实现,负责加载 Java 默认扩展目录下的 jar 包(%JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类)。

  3. 系统类加载器(System/App ClassLoader):也称为应用程序类加载器,由 Java 实现,负责加载用户类路径(classpath)下的所有 jar 包和类。

![[Pasted image 20240915225845.png]]

除了这三个内置的类加载器外,还可以自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现,以满足特殊的需求。例如,可以通过自定义类加载器来加载网络上的类,或者从数据库中加载类。

对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 equals)。

ClassLoader

除了 BootstrapClassLoader 是 JVM 自身的一部分之外,其他所有的类加载器都是在 JVM 外部实现的,并且全都继承自 ClassLoader 抽象类。这样做的好处是用户可以自定义类加载器,以便让应用程序自己决定如何去获取所需的类。

每个 ClassLoader 可以通过 getParent() 获取其父 ClassLoader,如果获取到 ClassLoadernull 的话,那么该类是通过 BootstrapClassLoader 加载的。由于 BootstrapClassLoader 由 C++ 实现,由于这个 C++ 实现的类加载器在 Java 中是没有与之对应的类的,所以拿到的结果是 null。

public abstract class ClassLoader {
  ...
  // 父加载器
  private final ClassLoader parent;
  @CallerSensitive
  public final ClassLoader getParent() {
     //...
  }
  ...
}

下面是一个获取 ClassLoader 的示例:

public class PrintClassLoaderTree {

    public static void main(String[] args) {

        ClassLoader classLoader = PrintClassLoaderTree.class.getClassLoader();

        StringBuilder split = new StringBuilder("|--");
        boolean needContinue = true;
        while (needContinue){
            System.out.println(split.toString() + classLoader);
            if(classLoader == null){
                needContinue = false;
            }else{
                classLoader = classLoader.getParent();
                split.insert(0, "\t");
            }
        }
    }

}

输出结果:

|--sun.misc.Launcher$AppClassLoader@18b4aac2
    |--sun.misc.Launcher$ExtClassLoader@53bd815b
        |--null

可以看出:

  • 自定义编写的 Java 类 PrintClassLoaderTreeClassLoaderAppClassLoader
  • AppClassLoader 的父 ClassLoaderExtClassLoader
  • ExtClassLoader 的父 ClassLoaderBootstrap ClassLoader,因此输出结果为 null。

自定义类加载器

除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader。如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader抽象类。

ClassLoader 类有两个关键的方法:

  • protected Class loadClass(String name, boolean resolve):加载指定二进制名称的类,实现了双亲委派机制。name 为类的二进制名称,resolve 如果为 true,在加载时调用 resolveClass(Class<?> c) 方法解析该类。

  • protected Class findClass(String name):根据类的二进制名称来查找类,默认实现是空方法。

官方 API 文档中写到:

建议 ClassLoader的子类重写 findClass(String name)方法而不是loadClass(String name, boolean resolve) 方法。

如果我们不想打破双亲委派模型,就需要重写 ClassLoader 类中的 findClass() 方法,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

实现自定义类加载器

以下是我们自行实现自定义类加载器的一个示例:

import java.io.*;

public class CustomClassLoader extends ClassLoader {

    private String pathToBin;

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] classData = loadClassData(name);
            return defineClass(name, classData, 0, classData.length);
        } catch (IOException e) {
            throw new ClassNotFoundException("Class " + name + " not found", e);
        }
    }

    private byte[] loadClassData(String name) throws IOException {
        String file = pathToBin + name.replace('.', File.separatorChar) + ".class";
        InputStream is = new FileInputStream(file);
        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
        int len = 0;
        while ((len = is.read()) != -1) {
            byteSt.write(len);
        }
        return byteSt.toByteArray();
    }
}

示例说明:

  • 构造器:接受一个字符串参数,这个字符串指定了类文件的存放路径。
  • 覆写 findClass 方法:当父类加载器无法加载类时,findClass 方法会被调用。在这个方法中,首先使用 loadClassData 方法读取类文件的字节码,然后调用 defineClass 方法来将这些字节码转换为 Class 对象。
  • loadClassData 方法:读取指定路径下的类文件内容,并将内容作为字节数组返回。

类加载器加载规则

JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。

对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。

public abstract class ClassLoader {
  ...
  private final ClassLoader parent;
  // 由这个类加载器加载的类。
  private final Vector<Class<?>> classes = new Vector<>();
  // 由 JVM 调用,用此类加载器记录每个已加载类。
  void addClass(Class<?> c) {
        classes.addElement(c);
   }
  ...
}

类加载器工作过程

类加载器(Class Loader)在 Java 虚拟机(JVM)中的工作过程是一个复杂而精细的流程。类加载器不仅负责加载类的字节码文件,还要确保类的正确性和初始化。

JVM(Java虚拟机)——类的生命周期与加载过程

类加载器的工作过程可以分为以下几个主要阶段:

  1. 加载(Loading):在加载阶段,类加载器负责读取类的二进制数据,并将其转化为 Class 对象。这一阶段包括以下几个步骤:
  • 查找或获取类的二进制数据:类加载器会根据类的全限定名(例如 com.example.MyClass)查找并加载类的字节码文件。
  • 生成 Class 对象:类加载器将字节码文件转化为 Class 对象,并存放在方法区中。
  1. 验证(Verification):验证阶段是为了确保类文件的字节码符合 Java 虚拟机的规范,防止恶意代码危害虚拟机。验证阶段主要包括以下几个子阶段:

    • 文件格式验证:确保字节流的格式符合 Class 文件格式规范。
    • 元数据验证:确保类的元数据信息(如常量池中的常量)正确无误。
    • 字节码验证:确保字节码指令符合 JVM 规范,不会导致非法操作。
    • 符号引用验证:确保符号引用能正确解析到实际存在的类、接口、方法或字段。
  2. 准备(Preparation):准备阶段主要是为类变量分配内存空间,并设置类变量的初始值。注意,这里的类变量指的是被 static 修饰的变量。实例变量则是在对象实例化时分配内存空间。

  3. 解析(Resolution):解析阶段是将符号引用转换为直接引用的过程。符号引用指的是类名、接口名、方法名等字符串形式的引用,而直接引用则指向目标对象在内存中的地址。

  4. 初始化(Initialization):初始化阶段是执行类构造器 (<clinit>) 方法的过程。在这个阶段,类中的静态变量会被赋予初始值,并执行静态块中的代码。初始化阶段还包括类中非静态方法的调用和类的实例化。

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

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

相关文章

HTML和CSS做一个无脚本的手风琴页面(保姆级)

一、前言 使用HTML和CSS做一个无脚本的手风琴页面。让知识以自己喜欢的方式进入脑子&#xff0c;适用于很多场景&#xff0c;比如以下&#xff1a; 【注&#xff1a;图片源自百度】 二、HTML框架 使用外部样式表&#xff0c;将CSS文件用link标签引入 整体框架如下图&#x…

基于微信小程序的游泳馆管理系统--论文源码调试讲解

2 关键技术介绍 2.1 SSM框架 开发信息管理系统的主流框架是SSM&#xff08;Spring Spring MVC MyBatis&#xff09;&#xff0c;SSM框架web层使用Spring MVC框架&#xff0c;使传输前后端数据变得简单&#xff1b;对于业务层使用Spring作为轻量级控制反转和面向切面的容器框…

Leetcode面试经典150题-39.组合总数

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如…

基于Ambari搭建hadoop生态圈+Centos7安装教程

当我们学习玩搭建hadoop的时候&#xff0c;未免也会遇见很多繁琐的事情&#xff0c;比如很多错误&#xff0c;需要解决。在以后公司&#xff0c;也不可能让你一个一个搭建hadoop&#xff0c;成千上万的电脑&#xff0c;你在一个个搭建&#xff0c;一个个报错&#xff0c;而且每…

深度学习与应用:人体关键点检测

实验二 深度学习与应用&#xff1a;人体关键点检测 1、 实验目的 了解人体关键点检测基础流程熟悉YOLOV7-pose模型结构掌握 YOLOv7-pose 模型的训练、Fine-tuning 以及推理的能力掌握YOLOV7-pose模型对实际问题的应用能力&#xff0c;了解如何在特定的场景和任务中应用该模型…

为什么git有些commit记录,只有git reflog可以看到,git log看不到?

文章目录 原因分析1. git log 只能显示 **可达的** 提交2. git reflog 记录所有引用的变更 常见导致 git log 看不到提交的原因1. git reset 操作2. git rebase 操作3. 分支删除4. git commit --amend5. 垃圾回收&#xff08;GC&#xff09;* 如何恢复 git log 看不到的提交&am…

QT实现升级进度条页面

一.功能说明 在Qt中实现固件升级的进度条显示窗口&#xff0c;你可以通过创建一个自定义的对话框&#xff08;Dialog&#xff09;来完成。这个对话框可以包含一个进度条&#xff08;QProgressBar&#xff09;、一些文本标签&#xff08;QLabel&#xff09;用于显示状态信息&am…

学生管理系统1.0版本

学生管理系统1.0版本有5个功能&#xff0c;即添加学生、删除学生、修改学生、查看全部学生、退出系统。 里面对添加重复学号、删除和修改不存在的学号等问题都有相应的解决办法。 代码区&#xff1a; Student.java package student;//快捷键Altinsert public class Student …

iOS 消息机制详解

应用 解决NSTimer、CADisplayLink循环引用。 二者都是基于runloop的定时器&#xff0c;由于处理事件内容不一样&#xff0c;runloop 每运行一次运行耗时就不一样&#xff0c;无法准确的定时触发timer的事件。 NSProxy 与 NSObject 如果继承自NSProxy 直接开始消息转发&…

【comfyUI工作流】一键生成专属欧美漫画!

现在你不需要在webui上手动设置一堆的参数 来将自己的照片转绘成欧美漫画插画 可以通过我制作的工作流一键完成转绘&#xff0c;更加效率便捷&#xff0c; 而且不需要你懂什么专业的AI绘画知识&#xff0c;会打开工作流&#xff0c;上传图片就可以 工作流特点 真实照片一键…

linux StarRocks 安装

一、检查服务器是否支持avx2&#xff0c;如果执行命令显示空&#xff0c;则不支持&#xff0c;那么安装后无法启动BE cat /proc/cpuinfo |grep avx2我的支持显示如下&#xff1a; 二、安装 docker run -p 9030:9030 -p 8030:8030 -p 8040:8040 -p 9001:9000 --privilegedtrue…

开源PHP导航网源码/精美简约网址导航收录网站/QQ技术导航程序

源码简介&#xff1a; 一款给力的开源PHP导航网源码&#xff0c;它不仅外观精美简约&#xff0c;还是个网址导航收录网站/QQ技术导航程序哦&#xff01; 在信息爆炸的时代&#xff0c;找网页就像大海捞针一样难。但是有了像PHP 导航网这样的神器&#xff0c;一切都变得简单了…

Gin框架入门(1)--路由搭建与Json处理

背景知识 为什么要使用Go框架 如果不使用框架&#xff0c;在创建服务器和调用端口时会遇到各种各样“奇怪”的问题&#xff08;就是出错的排查方向可能达到十几种&#xff09;&#xff0c;而且这些问题很难有相似性。同时作为适应于微服务的一门语言&#xff0c;代码的规范化…

【计算机组成原理】主存储器深度解析

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

基于SpringBoot+Vue的时尚美妆电商网站系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目源码、Python精…

Windows下安装Neo4j流程

Neo4j简介 Neo4j 是一个基于图形结构的 NoSQL 数据库&#xff0c;专门用于存储和管理图数据。与传统的关系型数据库不同&#xff0c;Neo4j 使用 图&#xff08;graph&#xff09;的形式来表示数据&#xff0c;其中数据点&#xff08;称为 节点&#xff09;通过 边&#xff08;…

GUI编程19:贪吃蛇小游戏及GUI总结

视频链接&#xff1a;21、贪吃蛇之界面绘制_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p21&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.游戏中用的的图片素材 1.贪吃蛇游戏的主启动类StartGame&#xff1b; package com.yundait.snake;import j…

【ArcGISPro】配置模块

ArcGIS Pro 配置类似于加载项&#xff0c;但提供了扩展应用程序的其他方法。它可以帮助您设计更贴近您组织品牌和工作流的 ArcGIS Pro 版本。 托管配置是比 Add-in 更高级别的自定义。 配置可以提高加载项安全级别并添加非管理员指定的已知文件夹。 配置可以提供比插件更广泛…

全国832个贫困县名单及精准扶贫脱贫数据(2016-2020.11)

自党的十八大以来&#xff0c;通过全党全国各族人民的共同努力&#xff0c;中国成功实现了现行标准下9899万农村贫困人口的全部脱贫&#xff0c;832个贫困县全部摘帽。 摘帽名单 2016年-2020.11全国832个贫困县名单及精准扶贫脱贫数据整理&#xff08;大数据&#xff09;https…

图解 TCP 四次挥手|深度解析|为什么是四次|为什么要等2MSL

写在前面 今天我们来图解一下TCP的四次挥手、深度解析为什么是四次&#xff1f; 上一片文章我们已经介绍了TCP的三次握手 解析四次挥手 数据传输完毕之后&#xff0c;通信的双方都可释放连接。现在客户端A和服务端B都处于ESTABLISHED状态。 第一次挥手 客户端A的应用进…