JVM系列——类加载,类加载器day3-2

181 篇文章 3 订阅
7 篇文章 0 订阅

类加载

加载

将类的字节码载入方法区中,内部采用C++的instanceKlassI描述java类,它的重要field有:

  1. _java_mirror即java的类镜像,例如对 string 来说,就是String.class,作用是把klass暴露给java使用
  2. _super即父类
  3. _fields即成员变量_methods 即方法
  4. _constants即常量池
  5. _class_loader即类加载器
  6. _vtable虚方法表
  7. _itable接口方法表

如果这个类还有父类没有加载,先加载父类
加载和链接可能是交替运行的

instanceKlass是存储在元空间中的,_java_mirror则是在堆中的

在这里插入图片描述

链接

实现验证,用于验证类是否符合JVM的规范,是安全性检查

准备

为static变量分配空间,设置默认值

  • static变量在JDK7之前存储于instanceKlass末尾,从JDK7开始,存储于_java_mirror末尾
  • static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
  • 如果static变量是final的基本类型,那么编译阶段值就确定了,赋值在准备阶段完成如果static变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成

解析

将常量池中的符号引用解析为直接引用

意思是在未进行解析之前,常量池中就算有这个类,他仅仅是表示一个符号而已,不具备类的特性,只有进行解析之后,他才是类

初始化

初始化即调用<cinit>()v,虚拟机会保证这个类的构造方法的线程安全
类的初始化时懒惰的:

  • main方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量或静态方法时,类会初始化
  • 子类初始化,如果父类还没初始化,会引发,类的初始化
  • 子类访问父类的静态变量,只会触发父类的初始化
  • Class.forName,会导致类的初始化
  • new会导致初始化

不会导致类初始化的情况:

  1. 访问类的static final静态常量(基本类型和字符串)不会触发初始化
  2. 类对象.class不会触发初始化
  3. 创建该类的数组不会触发初始化
  4. 类加载器的 loadClass方法
  5. Class.forName的参数2为false时

类加载器(Java8)

类加载器的层级

  1. Bootstrap ClassLoader(启动类加载器):加载JAVA_HOME/jre/lib的类,无法直接访问
  2. Extension ClassLoader(扩展类加载器):加载JAVA_HOME/jre/lib/ext的类,上级为Bootstrap,显示为null
  3. Application ClassLoader(应用程序类加载器):加载classpath的类,上级为Extension
  4. 自定义类加载:加载自定义的类,上级为Application

启动类加载器

使用启动类加载器进行类加载

Class.forName("类的引用地址")
//获取类加载器,调用getClassLoader()方法
loadClass.getClassLoader()
//指定加载
java -Xbootclasspath/a:. 类路径

-Xbootclasspath :表示设置bootclasspath
/a:. :表示将当前目录追加至bootclasspath后

java -Xbootclasspath :<new bootclasspath>
java -Xbootclasspath/a :<追加路径>
java -Xbootclasspath/p:<追加路径>

扩展类加载器

对类进行打包
//打包为jar包
jar -cvf jar包名.jar 需要打包的类路径
将打包完的类放置到lib目录下

打包完后在当前项目的目录下
拿到打包好的jar包之后,我们找到jdk的安装目录,找到jre下的lib下的ext目录,将其粘贴进去即可
在这里插入图片描述
此时你再去执行

Class.forName("类的引用地址")
//获取类加载器,调用getClassLoader()方法
loadClass.getClassLoader()

得到的就是显示ExtClassLoader对其进行了加载

看到这里有人就说我没有jre目录啊,我用的是java11。。。,那么接下来介绍一下Java8以上的
如下,你可以看到我的jdk17是没有jre的
在这里插入图片描述
我们进入bin目录找到jlink.exe,他的作用是帮我们生成联系(jre)
在这里插入图片描述打开终端运行如下代码(这里注意一下,是在jdk主目录里不是在bin里,不过这个不知道的话好像也不会看这个系列的文章)

 bin\jlink.exe --module-path jmods --add-modules java.desktop --output jre

在这里插入图片描述
此时jre就有了
在这里插入图片描述
这里仅是告诉你怎么生成jre而已,但是这个jre是没有ext的
在 JDK9 以后,这种扩展机制被模块化带来的天然扩展能力所取代。由于扩展类加载器是由 Java 代码实现的,开发者可以直接在程序中使用扩展类加载器来加载 Class 文件

双亲委派

指的是调用类加载器的loadClass方法时,查找类的规则

源码
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
源码注释翻译

加载具有指定二进制名称的类。此方法的默认实现按以下顺序搜索类:
调用findLoadedClass(String)检查该类是否已加载。
在父类加载器上调用loadClass方法。如果父对象为null,则使用构建在虚拟机中的类加载器。
调用findClass(String)方法来查找该类。
如果使用上述步骤找到该类,并且resolve标志为true,则该方法将在生成的类对象上调用resolveClass(class)方法。
鼓励类加载器的子类重写findClass(String),而不是此方法。
除非重写,否则该方法在整个类加载过程中与getClassLoadingLock方法的结果同步。

步骤
  1. 检查当前类是否已被加载(findLoadedClass方法)
  2. 若未被加载,检查当前类加载器是否有上级,有则委派上级进行查找(parent.loadClass)
  3. 若没有上级,则委派BootstrapClassLoader(findBootstrapClassOrNull)
  4. 若所有都找不到,则本类加载器调用findClass方法进行类加载

线程上下文类加载器

在使用JDBC时需要加载Driver驱动,但实际上不写也是可以的

Class.forName("com.mysql.jdbc.Driver")

在DriverManager的源码中采用了static静态代码块中的loadInitialDrivers方法

static{
	loadInitialDrivers();
}

但当我们获取他的类加载器时显示为null,表明时BootstrapClassLoader
,这样就会去jre/lib下找mysql-connector-java的jar包,但实际上根本没有驱动jar包理应不能加载,这是后就要看一下loadInitialDrivers()方法
工作方式:

  1. 使用ServiceLoader机制加载驱动:这里也是使用的应用程序类加载器进行加载打破了双亲委派机制
  2. 使用jdbc.drivers定义的驱动名加载驱动:调用Class.forName进行加载Class.forName( aDriver,true,classLoader.getSystemClassLoader()) 这里的SystemClassLoader就是ApplicationClassLoader,这里打破了双亲委派机制
ServiceLoader

他是Service Provider Interface接口(SPI)
有如下约定:
在jar包的META-INF/services包下,以接口全限定名为文件,文件内容是实现类的名称
按照此规定设计的jar包就可以配合ServiceLoader找到实现类进行实例化,实现解耦
按照下面的方式得实现类,体现了面向接口编程+解耦的思想,在多个框架中都有运用

ServiceLoader<接口类型>allImpls = ServiceLoader.load(接口类型.class);
Iterator<接口类型>iter = allImpls.iterator();
while(iter.hasNext()){
iter.next():
}
ServiceLoader.load方法

这里就用到了线程上下文类加载器,看到Thread.currentThread().getContextClassLoader()说明是获取了当前的线程,使用线程获取上下文类加载器,这个就是线程上下文类加载器

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
    }

这说明线程上下文类加载器其实就是当前线程使用的类加载器,也就是应用程序类加载器

自定义类加载器

使用场景
  1. 想加载非classpath随意路径中的类文件
  2. 都是通过接口来使用实现,希望解耦时,常用在框架设计
  3. 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于tomcat容器
使用步骤
  1. 继承 ClassLoader 父类
  2. 要遵从双亲委派机制,重写findClass方法

注意不是重写loadClass方法,否则不会走双亲委派机制

  1. 读取类文件的字节码
  2. 调用父类的 defineClass方法来加载类
  3. 使用者调用该类加载器的 loadClass方法
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class SelfDesignClassLoader extends ClassLoader{
    //重写findClass
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classPath = ""+name+".class";
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            Files.copy(Paths.get(classPath),byteArrayOutputStream);
            //得到字节数组
            byte[] bytes = byteArrayOutputStream.toByteArray();
            //将字节转*.class
            return defineClass(name,bytes,0,bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("class not found");
        }

    }
}

java9及以上类加载器

java9及以上后类加载已然出现变化
在这里插入图片描述
在这里插入图片描述
从这里看出
类加载器变成了

  1. BootClassLoader(启动类加载器):加载JAVA_HOME/jre/lib的类,无法直接访问
  2. PlatformClassLoader(平台类加载器):加载JAVA_HOME/jre/lib/ext的类,上级为Boot,显示为null
  3. AppClassLoader(应用程序类加载器):加载classpath的类,上级为 Platform
  4. CustomerClassLoader(自定义类加载):加载自定义的类,上级为App
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值