likes
comments
collection
share

JVM之类加载器

作者站长头像
站长
· 阅读数 73

前言

说起JVM大家肯定不陌生, 属实是面试八股文常考内容了, 但是身为初级程序员的我对其充满了熟悉和陌生, 由此诞生了本系列文章, 旨在学透JVM

本篇为 JVM基础全概况, 主要讲解 JVM是什么, 类加载机制以及自己实现一个类加载器

1. JVM是什么

jvm可以被理解为独立于当前系统的虚拟机, 是一种规范, 可以执行 class文件, 直接和操作系统进行交互

1.1 java和JVM的关系

比如我们使用 java语言写了以下代码

 public static void main(String[] args) {
        System.out.println("欢迎观看本篇文章");
    }

那么当我们执行当前 java文件时, 该文件会被编译成后缀为 .class的字节码文件, 但是字节码不是机器码, 操作系统无法识别, 这个时候 JVM会识别字节码文件并与操作系统进行交互

所以, java字节码是沟通 java和 JVM之间的桥梁

当然, JVM也不是 java独有的, 有一些语言也是基于 jvm的

JVM之类加载器

2. 类加载器

2.1 类加载器是什么

JVM的类加载是通过 CLassLoader及其子类来完成的

  • 启动类加载器(Bootstrap Class-Loader):负责加载 JAVA_HOME/lib 目录的
  • 扩展类加载器(Bootstrap Class-Loader): 负责加载 JAVA_HOME/lib/ext 目录的
  • 应用程序类加载器(Bootstrap Class-Loader): 负责加载用户路径 classpath上的类库
  • 自定义类加载器: 加载应用之外的类文件

2.2 类加载时机

类加载时机:

  • 遇到 new, getStatic, putStatic, invokeStatic
  • 对类进行反射调用时
  • 初始化一个类时, 发现其父类还没有初始化, 要先初始化其父类
  • 当虚拟机启动时, 会先将主类加载(main方法)

2.3 类加载的过程

类的生命周期:

  • 加载
    • JVM在该阶段将字节码从不同数据源转为二进制字节流加载到内存中, 并生成该类的 .class文件

      类加载途径

      • jar/war
      • jsp生成的class
      • 数据库中的二进制字节流
      • 网络中的二进制字节流
      • 动态代理生成的二进制字节流
  • 验证: 对二进制字节流进行校验, 保证其符合 JVM字节码规范, 主要分为以下几点
    • 确保二进制字节流格式符合预期
    • 方法调用的参数个数和类型是否正确
    • 确保变量在使用之前被初始化
    • 检查变量是否被赋予正确的值
    • 是否所有方法都遵守访问控制关键字的限定
  • 准备: JVM会在该阶段对类变量分配内存并初始化
  • 解析: 将常量池中的符号引用转换为直接饮用
    • 符号饮用: 是以一组符号来描述所引用的目标(编译时java类并不知道实际的地址, 以符号来替代)
    • 直接饮用: 通过对符号饮用进行解析, 找到时机内存地址
  • 初始化: 执行类构造器方法的过程
  • 使用
  • 卸载(销毁)

3. 自定义类加载器

目标: 自定义类加载器, 加载指定路径下的类 步骤:

  • 新建一个类 Test.java
  • 编译 Test.java到指定 lib目录
  • 自定义类加载器 HeroClassLoader继承 ClassLoader;
    • 重写 findClass()方法
    • 调用 defineClass()方法
  • 测试自定义类加载器

3.1 新建 Test类

直接写一个最简单的类, 里面有一个 ningXuan方法

package com.ningxuan.demo;

public class Test {
    public void ningXuan(){
        System.out.println("宁轩Blog");
    }
}

然后点击 idea右上角的小锤子进行编译

JVM之类加载器

在编译target/class下, 根据类文件的 package路径就可以找到我们需要的 Test.class文件了

JVM之类加载器

将这个 Test.class文件放到指定路径下, 也就是一会想让自定义类加载器去找类的路径, 我这边设置为 E:\lib\com\ningxuan\demo

E:\lib 是自定义的, 后面与类文件的包名相同

若不想使用上述方法, 也可以使用命令行 javac Test.java

3.2 自定义类加载器

就是重写ClassLoader类的findClass方法

package com.ningxuan.demo.juejin;

import java.io.*;

public class HeroClassLoader extends ClassLoader{

    private String classpath;

    public HeroClassLoader(String classpath){
        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException{
        try{
            // 输入流, 通过类的全限定名称加载文件到字节数组
            byte[] classData = getData(name);
            if (classData != null){
                // defineClass方法将字节数组数据转为字节码对象
                return defineClass(name, classData, 0, classData.length);
            }
        }catch (IOException ioException){
            ioException.printStackTrace();
        }
        return super.findClass(name);
    }

    // 加载类的字节码数据
    private byte[] getData(String className) throws IOException {
        // 根据className生成对应 class路径
        String path = classpath + File.separatorChar +
                className.replace('.', File.separatorChar) + ".class";
        try(InputStream in = new FileInputStream(path);
            ByteArrayOutputStream out = new ByteArrayOutputStream()){
            byte[] buffer = new byte[2048];
            int len = 0;
            while((len = in.read(buffer)) != -1 ) {
                out.write(buffer, 0, len);
            }
            return out.toByteArray();
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }
        return null;
    }
}

3.3 测试自定义类加载器

import java.lang.reflect.Method;

public class TestClassLoader {

    public static void main(String[] args) throws Exception{
        // 自定义类加载器的加载路径
        HeroClassLoader classLoader = new HeroClassLoader("E:\lib");
        // 报名加类名
        Class c =  classLoader.loadClass("com.ningxuan.demo");
        if (c != null){
            Object o = c.newInstance();
            Method method = c.getMethod("ningXuan", null);
            method.invoke(o, null);
            System.out.println(c.getClassLoader().toString());
        }
    }

}

执行结果如下, 成功打印

JVM之类加载器

4. 双亲委派模型

双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样。

双亲委派模型不是一种强制性约束,也就是你不这么做也不会报错怎样的,它是一种JAVA设计者推荐使用类加载器的方式。

同时使用双亲委派模型有一个很明显的好处, 那就是 java类随着他的类加载器一起具备了一种带有优先级的层次关系, 这对于保证 java程序的稳定运作很重要

本文内容到此结束了

如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问💬欢迎各位大佬指出。

我是 宁轩 , 我们下次再见

转载自:https://juejin.cn/post/7248967382961291325
评论
请登录