《Java 底层原理》Java 类加载器

前言

工作中需要实现一种功能:动态加载类对象信息,简单说就是class变了Jvm能够立马知道并且加载到内存。

类加载器分类

Java类加载器分为两种,一种是加载启动类,另一种是其他类加载器。Java加载类的关系:Launcher

1. 启动类加载器(BootstrapClassLoader)。

Java 程序是运行在Jvm上,所以Jvm需要知道Java程序的入口在何处(启动类)。Jvm通过c++的LoadMainClass去加载Java程序的sun.launcher.LauncherHelper。并且通过他加载Mian函数所在的扩展类ClassLOader和AppClassLoader。

2. Java自己编写的类加载器。

2.1 ExtClassLoader 扩展类加载器

ExtClassLoader类是没有父类的。

String var0 = System.getProperty("java.ext.dirs");

因为上面的代码,所以可以设置类扩展类加载器加载Jar包的目录。

2.2 AppClassLoader Class_path 指定的Jar包

AppClassLoader的父类为ExtClassLoader。我们main函数所在类的类加载器是AppClassLoader。

Java类加载器会加载的Jar包路径

import sun.misc.Launcher;

import java.net.URL;

public class ClassLoaderDemo1 {
    public static void main(String[] args) {
        String[] split = System.getProperty("sun.boot.class.path").split(";");
        for (int i = 0; i < split.length; i++) {
            System.out.println(split[i]);
        }
        System.out.println("========================================");
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urLs.length; i++) {
            System.out.println(urLs[i]);
        }
    }
}

运行结果:

委派机制

根据类加载器,画一个Java委派和加载的原理图。

Java不会被重新加载是通过类的全限定名来判断的

Java委派机制的好处。

1. 保证核心包加载不受干扰。

2.  保证class 加载唯一。

SPI机制

先看个案例

public interface ServerSPI {
    void execMethod();
}

两个实现类

public class ServerSPIImpl1 implements ServerSPI{
    @Override
    public void execMethod() {
        System.out.println("ServerSPIImpl1.execMethod 执行");
    }
}

public class ServerSPIImpl2 implements ServerSPI {
    @Override
    public void execMethod() {
        System.out.println("ServerSPIImpl2.execMethod 执行");
    }
}

配置(目录要一致,文件名是接口名)

文件内容(类的全限定名)

写一个测试类

import sun.misc.Service;
import java.util.Iterator;

public class SpiTest {
    public static void main(String[] args) {
        Iterator<ServerSPI> providers = Service.providers(ServerSPI.class);

        while(providers.hasNext()) {
            ServerSPI ser = providers.next();
            ser.execMethod();
        }
    }
}

运行结果:

ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);   -- 这个方式也可以获取加载的对象。

该案例没有使用Java的委派机制实现了类的加载功能。

Service 的源码重点:

private static final String prefix = "META-INF/services/";    -- 为什么目录是指定的。
    public static <S> Iterator<S> providers(Class<S> var0) throws ServiceConfigurationError {
        ClassLoader var1 = Thread.currentThread().getContextClassLoader();     -- 获取线程所在个类ClassLoader。
        return providers(var0, var1);
    }

SPI加载类的原理:

1. 需要指定目录,程序需要在知道目录下找到接口信息,根据接口找到实现类。

2. 通过线程所在的ClassLoader 加载实现,通过反射实例化对象。

3. 其他细节源码,有兴趣自己了解。

4. 我们经常使用的JDBC就是使用SPI机制实现类加载。

自定义类加载器

实现热部署功能案例:https://www.cnblogs.com/jssj/p/13251804.html

案例中很好的运用了自定义类加载器,并且实现了如何打破Java委派机制。

总结

类加载器的种类,类加载器的各自分工,类加载的委派过程,SPI机制,自定义加载器的实现。

原文地址:https://www.cnblogs.com/jssj/p/14285530.html