JVM 类加载器

目录

一、类加载过程

运行阶段,JVM中的类加载器读取.class文件,在方法区中创建该类的Class对象用于反射操作,对象中保存了类的格式和方法的字节码指令 ,。如果该类的父类还没有被加载,那么会首先加载父类

JVM运行时并不会一次性加载所有的类,而是根据需要动态请求类加载 。每个类加载器都有一个HashMap,记录<全限定类名,Class对象>,所以对同一个ClassLoader,二进制名称相同的类只会被加载一次

  • BootstrapClassLoader(启动类加载器):最顶层的加载类,由 C++实现,用来加载JDK的核心类库(%JAVA_HOME%/lib目录下)。
  • AppClassLoader(应用程序类加载器):负责加载当前应用中的所有自定义jar包和类(项目根目录下)。

二、双亲委派模型

对于同一个ClassLoader来说,同一个类只能被加载一次,但是不同的加载类可以加载同一个类,这就有类的重复加载问题 。双亲委派模型会先将类加载请求转发给父类加载器,确保了不会有重复加载问题。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。

此外,双亲委派模型还能防止核心API被篡改 ,当项目中有多个String.class类时,委派BootstrapClassLoader加载JDK中的String类防止AppClassLoader加载自定义的String类。

类加载过程如下:

  • 类加载时首先判断当前类是否被加载过,已经被加载的类会直接返回,否则才会尝试加载。
  • 类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这类加载请求委派给父加载器去完成 (调用父加载器loadClass()方法来加载类),这样的话,所有的请求最终都会传送到顶层的启动类加载器BootstrapClassLoader中。
  • 类加载器加载时首先判断父类是否被加载,如果没有则先加载父类
  • 只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载(调用自己的findClass()方法来加载类)。
  • 如果子类加载器也无法加载这个类,那么它会抛出一个ClassNotFoundException异常。
java 复制代码
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //首先,检查该类是否已经加载过
        Class c = findLoadedClass(name);
        if (c == null) {
            //如果 c 为 null,则说明该类没有被加载过
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //当父类的加载器不为空,则通过父类的loadClass来加载该类
                    c = parent.loadClass(name, false);
                } else {
                    //当父类的加载器为空,则调用启动类加载器来加载该类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //非空父类的类加载器无法找到相应的类,则抛出异常
            }

            if (c == null) {
                //当父类加载器无法加载时,则调用findClass方法来加载该类
                //用户可通过覆写该方法,来自定义类加载器
                long t1 = System.nanoTime();
                c = findClass(name);

                //用于统计类加载器相关的信息
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            //对类进行link操作
            resolveClass(c);
        }
        return c;
    }
}

三、自定义类加载器

由于AppClassLoader只能加载项目内的类,如果我们想加载项目外的类,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。

继承ClassLoader抽象类,重写以下方法:

  • findClass(String name):给定要加载的类的二进制名称,重写该方法后默认还是双亲委派机制先调用父加载器loadClass()方法加载,如果加载失败才调用该方法内的逻辑加载。
  • loadClass(String name, boolean resolve):重写该方法后类加载器会首先尝试自己加载name类。
相关推荐
fly spider29 分钟前
一文概括 JVM 核心内容
jvm
brahmsjiang31 分钟前
Java类加载机制解析:从JVM启动到双亲委派,再到Android的特殊实现
android·java·jvm
cch89181 小时前
C++、Python与汇编语言终极对比
java·开发语言·jvm
彧翎Pro10 小时前
基于 RO1 noetic 配置 robosense Helios 32(速腾) & xsense mti 300
前端·jvm
minji...14 小时前
Linux 线程同步与互斥(二) 线程同步,条件变量,pthread_cond_init/wait/signal/broadcast
linux·运维·开发语言·jvm·数据结构·c++
woai336415 小时前
JVM学习-基础篇-常见引用
jvm·学习
それども16 小时前
理解JVM参数 Xss 线程的栈大小
jvm
玛卡巴卡ldf16 小时前
【Springboot6】内存泄漏OOM、VisualVM、Arthas、Prometheus Grafana监控、垃圾回收
java·jvm·springboot
一个有温度的技术博主16 小时前
深入多级缓存:JVM进程缓存实战与数据库表拆分策略
jvm·数据库·缓存
码云数智-园园17 小时前
C# 内存模型的基石:值类型与引用类型的深度博弈
java·开发语言·jvm