开发者社区 > 博文 > Java类加载机制详解
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

Java类加载机制详解

  • jd****
  • 2023-10-23
  • IP归属:北京
  • 174浏览

    一.类加载器及双亲委派机制




    类加载器加载类备注
    启动类加载器(Bootstrap ClassLoader)JAVA_HOME/jre/lib无上级,无法直接访问 由jvm加载
    拓展类加载器(Extension ClassLoader)JAVA_HOME/jre/lib/ext父加载器为 Bootstrap,显示为 null 。该类由Bootstrap加载
    应用类加载器(Application ClassLoader)classpath父加载器上级为 Extension,该类由Bootstrap加载
    自定义类加载器自定义路径父加载器为 Application,该类由Application ClassLoader加载


    1.类加载器继承结构

    2. 类加载器的核心方法

    方法名说明
    getParent()返回该类加载器的父类加载器
    findClass(String name)查找名字为name的类,返回的结果是java.lang.Class类的实例
    loadClass(String name)加载名为name的类,返回java.lang.Class类的实例
    defineClass(String name,byte[] b,int off,int len)根据字节数组b中的数据转化成Java类,返回的结果是java.lang.Class类的实例



    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");
        }
    }
    


    文章数
    1
    阅读量
    74

    作者其他文章