Java 类加载机制详解 | 京东云技术团队
- 2023-10-25  北京
- 本文字数:6404 字 - 阅读完需:约 21 分钟 

一.类加载器及双亲委派机制
 
  
 1.类加载器继承结构
 
  
 2. 类加载器的核心方法
 
 3. Launcher 类源码解析
public class Launcher {    private static URLStreamHandlerFactory factory = new Factory();    private static Launcher launcher = new Launcher();    // 启动类加载器加载路径    private static String bootClassPath =        System.getProperty("sun.boot.class.path");
    public static Launcher getLauncher() {        return launcher;    }
    private ClassLoader loader;
    public Launcher() {        // Create the extension class loader        ClassLoader extcl;        try {            // 获取扩展类加载器            extcl = ExtClassLoader.getExtClassLoader();        } catch (IOException e) {            throw new InternalError(                "Could not create extension class loader", e);        }
        // Now create the class loader to use to launch the application        try {            // 获取应用类加载器            loader = AppClassLoader.getAppClassLoader(extcl);        } catch (IOException e) {            throw new InternalError(                "Could not create application class loader", e);        }
        // Also set the context class loader for the primordial thread.        // 设置线程上下文类加载器为应用类加载器        Thread.currentThread().setContextClassLoader(loader);    }
     /*     * The class loader used for loading installed extensions.     */    static class ExtClassLoader extends URLClassLoader {
               private static volatile ExtClassLoader instance = null;
        /**         * create an ExtClassLoader. The ExtClassLoader is created         * within a context that limits which files it can read         */        public static ExtClassLoader getExtClassLoader() throws IOException        {            if (instance == null) {                synchronized(ExtClassLoader.class) {                    if (instance == null) {                        instance = createExtClassLoader();                    }                }            }            return instance;        }        /**         * 获取加载路径         */        private static File[] getExtDirs() {            // 扩展类加载器加载路径            String s = System.getProperty("java.ext.dirs");        }
    }
    /**     * The class loader used for loading from java.class.path.     * runs in a restricted security context.     */    static class AppClassLoader extends URLClassLoader {
        public static ClassLoader getAppClassLoader(final ClassLoader extcl)            throws IOException        {            // 应用类加载器加载路径            final String s = System.getProperty("java.class.path");            final File[] path = (s == null) ? new File[0] : getClassPath(s);            return AccessController.doPrivileged(                new PrivilegedAction<AppClassLoader>() {                    public AppClassLoader run() {                    URL[] urls =                        (s == null) ? new URL[0] : pathToURLs(path);                    return new AppClassLoader(urls, extcl);                }            });        }}
4. ClassLoader 类源码解析
public abstract class ClassLoader { 
   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                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);                    sun.misc.PerfCounter.getFindClasses().increment();                }            }            if (resolve) {                resolveClass(c);            }            return c;        }    }
    // 自定义类加载器需要重写该方法    protected Class<?> findClass(String name) throws ClassNotFoundException {        throw new ClassNotFoundException(name);    }
}
5. 双亲委派机制优缺点
优点:
1、保证安全性,层级关系代表优先级,也就是所有类的加载,优先给启动类加载器,这样就保证了核心类库类
2、避免类的重复加载,如果父类加载器加载过了,子类加载器就没有必要再去加载了,确保一个类的全局唯一性
缺点:
检查类是否加载的委派过程是单向的, 这个方式虽然从结构上说比较清晰,使各个 ClassLoader 的职责非常明确, 但是同时会带来一个问题, 即顶层的 ClassLoader 无法访问底层的 ClassLoader 所加载的类
通常情况下, 启动类加载器中的类为系统核心类, 包括一些重要的系统接口,而在应用类加载器中, 为应用类。 按照这种模式, 应用类访问系统类自然是没有问题, 但是系统类访问应用类就会出现问题。
二.spi 接口及线程上下文类加载器
1.spi 接口定义及线程上下文加载的作用
Java 提供了很多核心接口的定义,这些接口被称为 SPI 接口。(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI 接口中的代码经常需要加载具体的实现类。那么问题来了,SPI 的接口是 Java 核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI 的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader 无法委派 AppClassLoader 来加载类。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。
    类加载传导规则:JVM 会选择当前类的类加载器来加载所有该类的引用的类。例如我们定义了 TestA 和 TestB 两个类,TestA 会引用 TestB,只要我们使用自定义的类加载器加载 TestA,那么在运行时,当 TestA 调用到 TestB 的时候,TestB 也会被 JVM 使用 TestA 的类加载器加载。依此类推,只要是 TestA 及其引用类关联的所有 jar 包的类都会被自定义类加载器加载。通过这种方式,我们只要让模块的 main 方法类使用不同的类加载器加载,那么每个模块的都会使用 main 方法类的类加载器加载的,这样就能让多个模块分别使用不同类加载器。这也是 OSGi 和 SofaArk 能够实现类隔离的核心原理。
2. spi 加载原理
当第三方实现者提供了服务接口的一种实现之后,在 jar 包的 META-INF/services/ 目录里同时创建一个以服务接口命名的文件,该文件就是实现该服务接口的实现类。而当外部程序装配这个模块的时候,就能通过该 jar 包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
JDK 官方提供了一个查找服务实现者的工具类:java.util.ServiceLoader
public final class ServiceLoader<S>    implements Iterable<S>{
     // 加载spi接口实现类配置文件固定路径    private static final String PREFIX = "META-INF/services/";   /**     * Creates a new service loader for the given service type, using the     * current thread's {@linkplain java.lang.Thread#getContextClassLoader     * context class loader}.     *     * <p> An invocation of this convenience method of the form     *     * <blockquote><pre>     * ServiceLoader.load(<i>service</i>)</pre></blockquote>     *     * is equivalent to     *     * <blockquote><pre>     * ServiceLoader.load(<i>service</i>,     *                    Thread.currentThread().getContextClassLoader())</pre></blockquote>     *     * @param  <S> the class of the service type     *     * @param  service     *         The interface or abstract class representing the service     *     * @return A new service loader     */    public static <S> ServiceLoader<S> load(Class<S> service) {        // 线程上下文类加载器         ClassLoader cl = Thread.currentThread().getContextClassLoader();        return ServiceLoader.load(service, cl);    }}
3.示列代码
代码:
public interface IShout {    void shout();}
public class Dog implements IShout {    @Override    public void shout() {        System.out.println("wang wang");    }}public class Cat implements IShout {    @Override    public void shout() {        System.out.println("miao miao");    }}
public class Main {    public static void main(String[] args) {        ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);        for (IShout s : shouts) {            s.shout();        }    }}
配置:
 
 4.MySQL 驱动类加载
 
 // 加载Class到AppClassLoader(系统类加载器),然后注册驱动类//Class.forName("com.mysql.jdbc.Driver").newInstance();String url = "jdbc:mysql://localhost:3306/testdb";// 通过java库获取数据库连接Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");
public class DriverManager {    static {        loadInitialDrivers();        println("JDBC DriverManager initialized");    }private static void loadInitialDrivers() {         。。。。。。。        AccessController.doPrivileged(new PrivilegedAction<Void>() {            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                /* Load these drivers, so that they can be instantiated.                 * It may be the case that the driver class may not be there                 * i.e. there may be a packaged driver with the service class                 * as implementation of java.sql.Driver but the actual class                 * may be missing. In that case a java.util.ServiceConfigurationError                 * will be thrown at runtime by the VM trying to locate                 * and load the service.                 *                 * Adding a try catch block to catch those runtime errors                 * if driver not available in classpath but it's                 * packaged as service and that service is there in classpath.                 */                try{                    while(driversIterator.hasNext()) {                        driversIterator.next();                    }                } catch(Throwable t) {                // Do nothing                }                return null;            }        });
        println("DriverManager.initialize: jdbc.drivers = " + drivers);
        if (drivers == null || drivers.equals("")) {            return;        }        String[] driversList = drivers.split(":");        println("number of Drivers:" + driversList.length);        for (String aDriver : driversList) {            try {                println("DriverManager.Initialize: loading " + aDriver);                Class.forName(aDriver, true,                        ClassLoader.getSystemClassLoader());            } catch (Exception ex) {                println("DriverManager.Initialize: load failed: " + ex);            }        }    }}
三.自定义动态类加载器
1.示例代码
public class DynamicClassLoad extends ClassLoader{
    public static void main(String[] args) {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() {            @Override            public void run() {                try {                    DynamicClassLoad myClassLoad = new DynamicClassLoad();                    Class clazz = myClassLoad.findClass("/Users/wangzhaoqing1/Desktop/MyTest.class");                    Object obj = clazz.newInstance();                    Method sayHello = clazz.getDeclaredMethod("sayHello");                    sayHello.invoke(obj, null);                } catch (Throwable e) {                    e.printStackTrace();                }            }        }, 1, 2, TimeUnit.SECONDS);    }
    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        File file = new File(name);        try {            byte[] bytes = FileUtils.readFileToByteArray(file);            Class<?> c = this.defineClass(null, bytes, 0, bytes.length);            return c;        } catch (Exception e) {            e.printStackTrace();        }        return super.findClass(name);    }}
// DynamicClassLoad启动后,修改本类重新编译public class MyTest {
    public void  sayHello(){        System.out.println("hello wzq 6666666666");    }}
作者:京东零售 王照清
来源:京东云开发者社区 转载请注明来源
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/f210824fbf8fee05d4aea8ccc】。文章转载请联系作者。

京东科技开发者
拥抱技术,与开发者携手创造未来! 2018-11-20 加入
我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩










 
    
评论