简单了解类加载

什么是类?

类是Java中一个很重要的概念,区别于面向过程编程,面向对象编程把一些现实中的东西抽象成一个类,如手机类、电脑类,他们都会有一些属性(CPU、内存等),也会有一些方法(开机、关机等)

类加载又是什么?

前面提到的类实际上就是一个 .java 为后缀的文件,但是如果想要将这个类放到其它机器上运行,就需要先将其编译成 .class 文件,利于计算机理解和网络传输,目标主机在运行程序的时候将会加载这个 .class 文件,获得类的信息,这就是类加载

.class文件有什么?

既然 .class 文件是 .java 编译过来的,自然就能想到一些方法、字段的元数据信息(存放在常量池中),除此之外还有一些文件信息

  • 文件信息:如开头的魔数确认这是个 java 文件,以及其它的还可以确认 JDK 的版本号(当前值 - 54 = 当前 JDK 版本号)
  • 常量池:存放着符号常量,如方法名、字段名、类名等类元数据信息

类加载过程

经常会看到加载、连接、初始化,更详细些就是加载、验证、准备、解析、初始化

  • 加载:类加载器会把 jar 包里面的 .class 文件通过二进制流的方式加载到方法区,同时在内存生成一个 Class 对象指向这个类元数据
  • 连接
    • 验证:验证这个 .class 文件是否符合规范、是否对 JVM 有危害
    • 准备:将类属性(static修饰的字段)进行赋初值,引用属性 = null;普通属性 = 0;如果是 final ,直接赋程序内的值
    • 解析:将常量池的符号引用(不一定加载到 JVM 中)转化为直接引用(已经加载到 JVM 中,有对应的字段或者方法的地址);由于类的动态绑定,这个过程可能在初始化之后执行
  • 初始化:执行 JVM 创建的初始化函数 init(),将 static 代码块或者 static 变量的初始化执行,和构造器不同,只会执行一次

什么时候会进行类加载?

  1. 创建类的实例,new对象
  2. 调用静态方法或者静态字段
  3. 调用反射
  4. JVM启动加载一些系统类

类加载的几种方式

// 只会加载class文件
getClassLoader().loadClass("com.xxx.xxx.D");
// 还会进行连接操作
Class.forName("com.xxx.xxx.D");
// 加载class文件并且初始化
D d = new D();

类加载器

在加载的时候,我们依靠内存中的类加载器进行类的加载

三个重要的类加载器

  • BootstrapClassLoader:启动类加载器,C++实现,获取的结果是null,<JAVA_HOME>\lib
  • ExtensionClassLoader:拓展类加载器,<JAVA_HOME>\lib\ext
  • AppClassLoader:应用类加载器,java -classpath(即在main包下的 .class 文件);一般情况下,该类加载器是程序的默认类加载器,我们可以通过ClassLoader.getSystemClassLoader()方法可以直接获取到它。

自定义类加载器

如果需要自定义类加载器,那么只需要继承ClassLoader类即可,但继承ClassLoader需要自己重写findClass()方法并编写加载逻辑

所以如果一般没有太过复杂的需求,可以直接继承URLClassLoader类,可以省略自己编写findClass方法以及文件加载转换成字节码流的步骤

  • 如果不想打破双亲委派机制,就重写findClass()
  • 如果想打破双亲委派机制,还需重写loadClass()
public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) {
        // class文件路径
        String path = "E:\\" + name + ".class";

        // 根据路径读取文件到字节输出流
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        Files.copy(Paths.get(path), os);
        
        byte[] bytes = os.toByteArray();

        // 加载类
        return defineClass(name, bytes, 0, bytes.length);
    }
}

双亲委派模型

只有父加载器反馈无法加载,才会让子加载器自己加载

目的是尽量让最顶层的BootstrapClassLoader来创建当前的类

双亲委派模型的好处

避免核心类冲突,因为都是启动类加载

避免类的重复加载(从同一个Class文件加载,类名相同,但是类的类加载器不同也是不同的类)

双亲委派模型的核心代码

重写loadClass()打破双亲委派机制

  1. 先检查类是否被加载过
  2. 调用父加载器加载
  3. 调用自己的加载器加载,可以通过重写findClass()自定义类加载器
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 {
                    // 当父类的加载器为空,则调用启动类加载器BootstrapClassLoader来加载该类
                    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;
    }
}            

打破双亲委派模型的例子

Tomcat

数据库驱动

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值