likes
comments
collection
share

JVM-类加载及类加载器

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

本文是JVM系列第二篇

类加载器的作用和分类

类加载器(Class Loader)是Java虚拟机的一个重要组成部分,它负责将Java类加载到内存中,并将其转换成Java对象。类加载器的作用是将Java程序的字节码加载到内存中,并将其转换成Java对象,以便在Java虚拟机中执行。

类加载器的分类如下:

  1. 启动类加载器(Bootstrap Class Loader):负责加载Java平台核心库,如rt.jar等。启动类加载器是Java虚拟机的一部分,是用C++编写的,不是Java类,因此无法在Java程序中直接访问。
  2. 扩展类加载器(Extension Class Loader):负责加载Java平台扩展库,如jce.jar等。扩展类加载器是由Java语言编写的,它是Java虚拟机类加载器的子类。
  3. 应用程序类加载器(Application Class Loader):负责加载应用程序的类,如自己编写的类和第三方类库。应用程序类加载器也是由Java语言编写的,它是Java虚拟机类加载器的子类。
  4. 自定义类加载器:可以根据需要自定义类加载器,用于加载一些特殊的类,如加密过的类等。

JVM-类加载及类加载器 类加载器的作用主要包括:

  1. 加载类:将Java程序的字节码文件加载到内存中,转换成Java对象并初始化。
  2. 搜索类:在类路径中搜索指定的类,找到后将其加载到内存中。
  3. 命名空间隔离:每个类加载器都有独立的命名空间,可以避免不同类之间的冲突。

总之,类加载器是Java虚拟机的重要组成部分,负责将Java类加载到内存中,以便在Java虚拟机中执行。类加载器可以根据需要自定义,也可以根据功能和权限进行分类。

类加载过程及原理

类加载是Java程序运行的重要过程之一,它将Java类加载到内存中,并将其转换成Java对象以便在Java虚拟机中运行。类加载过程分为三个阶段:加载、链接和初始化。

JVM-类加载及类加载器

  1. 加载:在加载阶段,类加载器会从指定的位置(例如本地文件系统、网络等)加载类的字节码文件到内存中,并生成相应的Class对象。在加载阶段,Java虚拟机不会对类进行任何的链接和初始化操作。
  2. 链接:在链接阶段,Java虚拟机会对类进行验证、准备和解析等操作。
  • 验证:验证阶段主要是对类的字节码进行验证,以确保它符合Java虚拟机规范,不会出现安全问题和运行时异常。
  • 准备:准备阶段主要是为类的静态变量分配内存,并初始化为默认值,如int类型的变量初始化为0,对象类型的变量初始化为null等。
  • 解析:解析阶段主要是将类中的符号引用转换成直接引用,以便在运行时可以直接访问类中的方法和字段。
  1. 初始化:在初始化阶段,Java虚拟机会执行类的初始化代码,包括静态代码块和静态变量的赋值等操作。

在Java虚拟机规范中,只有在以下情况下才会执行类的初始化代码:

  • 创建类的实例;
  • 访问类的静态变量或者静态方法;
  • 使用反射机制访问类的方法或者字段;
  • 初始化类的子类;
  • 启动Java虚拟机时,指定的类。

代码执行顺序举例

public class MethodOrder {
    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println("-----------------");
        dog.instanceMethod();
        System.out.println("-----------------");
        Dog.staticMethod();
        System.out.println("-----------------");
        Dog dog2 = new Dog("Jerry");
        dog2.instanceMethod();
        System.out.println("-----------------");
    }
}
class Animal {
    static {
        System.out.println("Animal 静态代码块");
    }

    {
        System.out.println("Animal 构造代码块");
    }

    public Animal() {
        System.out.println("Animal 无参构造函数");
    }

    public Animal(String name) {
        System.out.println("Animal 有参构造函数");
    }

    public static void staticMethod() {
        System.out.println("Animal 静态方法");
    }

    public void instanceMethod() {
        System.out.println("Animal 实例方法");
    }
}

class Dog extends Animal {
    private static String name = "default";

    static {
        System.out.println("Dog 静态代码块");
    }

    {
        System.out.println("Dog 构造代码块");
    }

    public Dog() {
        super("Tom");
        System.out.println("Dog 无参构造函数");
    }

    public Dog(String name) {
        super(name);
        System.out.println("Dog 有参构造函数");
    }

    public static void staticMethod() {
        System.out.println("Dog 静态方法");
    }

    public void instanceMethod() {
        System.out.println("Dog 实例方法");
    }
}

执行顺序如下

Animal 静态代码块
Dog 静态代码块
Animal 构造代码块
Animal 有参构造函数
Dog 构造代码块
Dog 无参构造函数
-----------------
Dog 实例方法
-----------------
Dog 静态方法
-----------------
Animal 构造代码块
Animal 有参构造函数
Dog 构造代码块
Dog 有参构造函数
Dog 实例方法
-----------------

类加载器的双亲委派模型

类加载器的双亲委派模型是一种类加载机制,它是Java中默认的类加载器行为。

在该模型中,当一个类加载器接收到类加载请求时,它会优先将该请求委派给它的父类加载器来尝试加载这个类。只有当父类加载器无法加载该类时,子类加载器才会尝试加载该类。

这样的设计有以下几个好处:

  1. 避免重复加载:如果父类加载器已经加载了某个类,那么子类加载器就没有必要再次加载该类,从而避免了重复加载,减少了内存占用和冲突问题。
  2. 安全性保障:通过双亲委派模型,可以确保核心Java API类由引导类加载器加载,而不是被自定义的类加载器所替代,从而避免了恶意代码替换核心API类的安全隐患。
  3. 代码隔离:每个类加载器都有自己的命名空间,从而实现了代码的隔离,不同类加载器加载的类互相之间不会产生影响,避免了类冲突和版本不兼容问题。

类加载器的自定义

public class CustomClassLoader extends ClassLoader {
    private final Map<String, Class<?>> classes = new HashMap<>(); // 缓存已加载的类
    private final String classPath; // 类文件路径
    private final Set<String> allowedPackages; // 允许加载的包名列表

    public CustomClassLoader(String classPath, Set<String> allowedPackages) {
        this.classPath = classPath;
        this.allowedPackages = allowedPackages;
    }

    public synchronized void addClass(String name, byte[] bytes) {
        classes.put(name, defineClass(name, bytes, 0, bytes.length));
    }

    @Override
    public synchronized Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> clazz = classes.get(name);
        if (clazz != null) {
            return clazz;
        }
        if (!isAllowed(name)) {
            throw new ClassNotFoundException("Class " + name + " not allowed");
        }
        try {
            // 使用双亲委派模型加载类
            return super.loadClass(name);
        } catch (ClassNotFoundException e) {
            // 如果双亲委派模型加载失败,则调用自定义逻辑加载类
            byte[] bytes = getClassData(name);
            if (bytes == null) {
                throw new ClassNotFoundException(name);
            }
            return defineClass(name, bytes, 0, bytes.length);
        }
    }

    private boolean isAllowed(String name) {
        for (String allowedPackage : allowedPackages) {
            if (name.startsWith(allowedPackage)) {
                return true;
            }
        }
        return false;
    }

    private byte[] getClassData(String name) {
        String path = classPath + File.separatorChar + name.replace('.', File.separatorChar) + ".class";
        try (InputStream inputStream = new FileInputStream(path)) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            return null;
        }
    }
}

在上述示例中,我们定义了一个CustomClassLoader类,继承自ClassLoader,并重写了loadClass()方法来实现类的加载逻辑。在加载类的过程中,我们先尝试使用双亲委派模型加载类,如果无法加载,则调用自定义逻辑从指定路径加载。

此外,我们还添加了缓存已加载的类、允许加载的包名列表等功能,以提高类加载的性能和安全性。

使用此自定义类加载器,我们可以加载特定目录下的类,并且只允许加载指定的包名,从而实现类隔离和安全性控制。

例如,我们可以使用以下代码加载特定目录下的类,并只允许加载指定的包名:

Set<String> allowedPackages = new HashSet<>();
allowedPackages.add("com.example.package1");
allowedPackages.add("com.example.package2");

CustomClassLoader loader = new CustomClassLoader("path/to/classes", allowedPackages);
Class<?> clazz = loader.loadClass("com.example.package1.MyClass");

在上述代码中,我们创建了一个CustomClassLoader类加载器,指定了类文件所在的路径以及允许加载的包名列表。然后,通过调用loadClass()方法加载指定的类。如果类不在允许加载的包名列表中,则会抛出ClassNotFoundException异常。

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