完整教程:类加载_JAVA学习笔记

一、类加载是什么?

Java 代码不是直接执行的,而是先编译成字节码(.class 文件),再由 JVM 执行。
但 JVM 并不知道所有类的位置,它必须通过一种机制去:

“把类文件加载进内存,变成可以用的Class对象”

这个过程就叫——类加载(Class Loading)

1.类加载器是什么?

比喻理解:

把JVM想象成一个图书馆,类加载器就是图书管理员,负责:

  • 找到需要的书(.class文件)
  • 检查书是否合格(验证)
  • 给书分配书架位置(准备)
  • 把书的内容整理好(解析)
  • 让书可以借阅(初始化)

2.类加载时机

类加载器时机 =  什么时候请管理员找书?

// 场景1:创建对象 - "我要借这本书"
Student stu = new Student();  //  管理员去找Student类
// 场景2:使用静态成员 - "我要查这本书的目录"
System.out.println(Student.school);  //  管理员去找Student类
// 场景3:继承关系 - "我要借这套丛书的所有分册"
class CollegeStudent extends Student {}  //  管理员会先找Student类,再找CollegeStudent类

二、类加载过程详解

三步曲:加载 → 链接 → 初始化

.class文件
   │
   ▼
[加载] → 创建Class对象 → 放入方法区
   │
   ▼
[链接] → 验证 → 准备 → 解析
   │
   ▼
[初始化] → 赋值/执行静态块

1.加载(Loading)

.class 文件从硬件、网络、JAR包等地方加载内存中。(通俗理解是:找书和登记)

流程:

  • 根据全限定类名(包名+类名)找到文件;
  • 读取字节流;
  • 生成一个 Class 对象放进方法区(Metaspace)
  • JVM 内部会用这个 Class 对象来创建实例、调用静态方法。
// 管理员根据"书名"找到.class文件
// 创建这本书的"借阅卡"(Class对象)
Class studentClass = Student.class;  //  创建借阅卡

2.链接(Linking)

链接包含三个子步骤
步骤含义举例
验证 (Verification)检查字节码格式是否符合 JVM 规范、安全性检查防止恶意或错误字节码
准备 (Preparation)为类的静态变量分配内存,设置默认值static String school = null;
解析 (Resolution)把符号引用(类名、方法名)转换成直接引用(内存地址)把 `"com.itheima.C"` → C类对象的地址

详情:

2.1.验证

检查书是不是正版,有没有危险内容

// 验证字节码是否符合JVM规范
// 防止恶意代码破坏图书馆

2.2.准备

给静态变量分配空间,设默认值

class Student {
    static String school = "大学";  // 准备阶段:school = null
    static int count;                 // 准备阶段:count = 0
}

2.3.解析

把符号引用变成直接引用

class B {
    C c = new C();  // 解析前:C只是个名字(符号)
                    // 解析后:C变成具体的内存地址
}

3.初始化

执行程序员写的初始化逻辑(赋值、静态代码块)

class Student {
    static String school = "大学";
}

经过初始化后:

静态区:String school = "大学";

也就是说:到这个阶段,类才算真正可用。

三、类加载器的体系结构

JVM使用不同的加载器来加载不同来源的类。

这些类加载器存在父子关系(上下级关系),但并不是代码层面的 extends,仅仅是逻辑层面的

比喻:把JVM想象成一个大型集团公司

          JVM集团公司
              │
    ┌─────────┼─────────┐
    │         │         │
    ▼         ▼         ▼
  董事长    部门经理   项目经理
 (启动类)   (平台类)   (应用类)

1.启动类加载器——“董事长”

  • 身份:身为创始人兼董事长,由 C++ 实现(看不到源码,null 表示)
  • 职责:只信任公司最核心的员工,也就是JDK核心类库
    • 负责加载 JDK 核心类,主要加载内容:
      • java.lang.*(如 String、Object)
      • java.util.*(如 List、Map)
      • java.io.*(如 File)

特点:

  • 是最底层的加载器
  • 因为是C++写的,所以我们在Java代码里打印时他会显示 null。
System.out.println(String.class.getClassLoader());
// 输出 null,因为由Bootstrap加载

(意思不是“没有加载器”,而是“被董事长亲自录用了”)

2.平台类加载器——“部门经理”

  • 职责:负责加载 扩展模块或中间层的工具类库
    • 主要加载内容:
      • JDK自带但非核心的包(比如javax.*、一些内部工具类)。
      • JDK 9 之后它取代了旧的 “扩展类加载器(ExtClassLoader)”。
  • 特点:
    • 承上启下:向董事长汇报,管理项目经理
    • 管专业模块:负责特定的扩展功能
    • JDK升级改名:从"扩展经理"晋升为"平台经理"
ClassLoader loader = DNSNameService.class.getClassLoader();
System.out.println(loader);
// 输出:jdk.internal.loader.ClassLoaders$PlatformClassLoader@xxxx

类比理解:

董事长不会亲自面试所有人,所以让部门经理负责审核“中层骨干”(JDK 扩展模块)。

部门经理(平台类加载器)专门管理像"网络服务"、"安全模块"这些专业部门,不上报给董事长的小事他都自己处理。

3.应用程序类加载器——“项目经理”

  • 职责:负责加载 我们自己写的代码
    • 主要加载内容:
      • classpath 路径下的所有类(也就是你自己写的 .class 文件)。
      • 几乎所有用户代码都是它加载的。
  • 特点:
    • 一线管理:直接管理我们写的代码
    • 最常用:95%的类都是他加载的
    • classpath专家:负责项目路径下的所有类
public class MyClass {
    public static void main(String[] args) {
        // 项目经理管理我们自己的代码
        System.out.println("我的类加载器: " + MyClass.class.getClassLoader());
        // 输出: sun.misc.Launcher$AppClassLoader@xxx
        System.out.println("String类加载器: " + String.class.getClassLoader());
        // 输出: null (董事长管的)
    }
}

通俗理解:

项目经理(应用程序类加载器)就是我们直接打交道的"直属领导",我们写的所有代码都归他管。

4.自定义类加载器——“外包团队”

  • 职责:自己定义从哪里加载类,比如:
    • 从网络加载;
    • 从加密的 jar 包中加载;
    • 从数据库或云端拉取 class 数据
  • 关键点:
    • 继承 ClassLoader;
    • 通常重写 findClass() 方法;
    • 默认“上级”是应用类加载器。
  • 特点:
    • 特殊任务:处理常规加载器做不到的事情
    • 灵活机动:可以自定义加载逻辑
    • 向上汇报:默认向项目经理汇报
// 比如:从网络加载类、加载加密的class文件等
public class NetworkClassLoader extends ClassLoader {
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        // 自定义加载逻辑:从网络下载字节码
        byte[] classData = downloadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }
    private byte[] downloadClassData(String className) {
        // 模拟从网络下载
        return new byte[0];
    }
}

类比理解:

你公司外包了一个特殊项目,需要一个外部顾问来“按自己方式”招人。

5.完整的工作流程演示

public class ClassLoaderDemo {
    public static void main(String[] args) {
        System.out.println("=== 类加载器完整演示 ===");
        // 1. 查看各种类的加载器
        showClassLoaders();
        // 2. 查看层级关系
        showHierarchy();
    }
    static void showClassLoaders(){
        System.out.println("\n1.各类的加载器:");
        //董事长管理
        System.out.println("String (核心类):"+String.class.getClassLoader());
        System.out.println("List (工具类):"+java.util.List.class.getClassLoader());
        //项目经理管理
        System.out.println("当前类:"+ClassLoaderDemo.class.getClassLoader());
    }
    static void showHierarchy(){
        System.out.println("\n2. 加载器层级关系:");
        ClassLoader loader = ClassLoaderDemo.class.getClassLoader();
        System.out.println("我 → " + loader);  // 项目经理
        System.out.println("我上级 → " + loader.getParent());  // 部门经理
        System.out.println("我上上级 → " + loader.getParent().getParent());  // 董事长(null)
    }
}

6.双亲委派模式

6.1.什么是双亲委派?

这个体系不是“代码继承”,而是“上下级汇报制度”。比喻: 就像公司里的请示汇报制度
工作原则(父类委派机制):

当一个下级加载器接到“加载任务”时,不会立刻干,而是先请上级试试看。

具体步骤:
当我 new String() 时:
1. 项目经理收到任务
2. 项目经理问部门经理:你能加载String吗?
3. 部门经理问董事长:你能加载String吗?
4. 董事长:我能!我来加载
5. 最终:String由董事长加载完成

public class DoubleParentDemo {
    public static void main(String[] args) {
        String str = new String("hello");
        System.out.println(String.class.getClassLoader());  // 输出:null
    }
}

6.2.双亲委派的好处:

  1. 避免重复加载:同一个类只加载一次
  2. 安全防护:防止核心API被篡改
  3. 结构清晰:各司其职,职责分明
posted @ 2025-12-01 22:43  gccbuaa  阅读(0)  评论(0)    收藏  举报