美文网首页
一、类加载机制

一、类加载机制

作者: 此间有道 | 来源:发表于2020-06-10 22:37 被阅读0次

    1. 前置问题

    1. 类加载顺序是什么?是严格有序的吗?
    2. 验证过程验证的什么内容?
    3. 什么是双亲委派?为什么要采用双亲委派?
    4. 什么是全盘委托?
    5. 什么情况下需要自定义类加载器?
    6. 三种类加载器分别加载哪里的类?
    7. ClassLoader,URLClassLoader, AppClassLoader的各自职责?
    8. findClass和loadClass的区别?
    9. UrlClassPath的实现原理?
    10. 自定义类加载器一般需要实现哪个方法?
    11. 自己写一个String类可以被classloader加载吗?
    12. 类加载到哪里了?方法区还是堆?

    1. JVM结构图

    内存结构图

    2. 类加载顺序

    类加载过程

    类加载是指类加载器把类字节码加载到方法区,然后在堆上创建对应的class对象来封装对方法区的数据结构。

    加载 -> 验证 -> 准备 -> 解析 -> 初始化
    1)加载:classloader从其可见的资源路径中加载指定的java字节码,即class文件;
    2)验证:验证class文件的正确性;
    3)准备:给类的静态变量分配空间并赋予默认值;
    4)解析:解析符号引用为直接引用,其中符号指类的静态方法的方法名替换为该方法所在的内存地址指针,也叫直接引用
    5)初始化:执行类构造器方法<clinit>,即把类的静态变量初始化为指定的值,执行静态代码块。

    这个顺序是开始执行的顺序,不是串行执行,通常是在一个过程执行中触发下一个过程。

    相关的Java方法

    1)java.lang.ClassLoader#loadClass(String className, boolean resolve)
    resolve=true,类加载后执行连接过程;
    resolve=false,类加载后不执行连接过程;

    2)Class.forName(String className)
    执行加载,连接,初始化(即执行<clinit>())的整个过程。

    数据库驱动注册与Class.forName
    package com.mysql.cj.jdbc;
    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        public Driver() throws SQLException {
        }
    
        // 数据库驱动注册时必须使用Class.forName加载类,以此来保证执行注册逻辑的静态代码块
        static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    }
    

    3. 类加载器

    1)加载器类型

    加载器 实现 加载路径 备注
    启动类加载器 C++实现 加载jre/lib下的rt.jar等核心包
    扩展类加载器 Java实现 加载jre/lib/ext扩展目录的包 Lancher的内部类
    应用加载器 Java实现 加载classpath的包 Lancher的内部类
    自定义类加载器 Java实现 自定义路径

    2)类继承结构


    类继承结构
    职责 关键方法
    ClassLoader 1)实现双亲委派;2)加载类字节码到内存; loadClass(), defineClass()
    URLClassLoader 根据URL定位资源 findClass(), getResource(), getResources()
    AppClassLoader 定义应用类加载器的资源路径(System.getProperty("java.class.path"))
    ExtClassLoader 定义扩展类加载器的资源路径(System.getProperty("java.ext.dirs"))

    4. 双亲委派和全盘委托

    双亲委派

    1)双亲委派是指先由父加载器去加载,如果父加载器无法找到类,再由自身加载。
    2)全盘委托是指当一个ClassLoader装载一个类时,除非显示地使用另一个ClassLoader,否则该类所依赖及引用的类也由这个CladdLoader载入。

    5. SPI的ServiceLoader加载类原理

    由于ServcieLoader类加载器的是启动类加载器,其无法加载应用的类,故获取当前线程的上下文类加载器来加载META-INF/services下声明的类。

    public final class ServiceLoader<S> implements Iterable<S> {
      public static <S> ServiceLoader<S> load(Class<S> service) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return ServiceLoader.load(service, cl);
      }
    }
    

    6. 自定义类加载器

    应用开发时有些业务场景往往需要自定义类加载器,原因一般有以下几种:
    1)资源隔离;例如多租户系统,Tomcat的多应用等
    2)热加载,热部署;
    3)加载非classpath中的资源;
    以上的情景中有些需要打破双亲委派;

    7. 自定义的java.lang.String类无法被加载

    1)如果不自定义类加载器,则双亲委派机制始终加载rt.jar的String类;
    2)如果自定义类加载器,打破双亲委派,但是由于defineClass里约束了不能加载以java.起始的类,会抛出异常。

    public abstract class ClassLoader {
     protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError {
            protectionDomain = preDefineClass(name, protectionDomain);
            String source = defineClassSourceLocation(protectionDomain);
            Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
            postDefineClass(c, protectionDomain);
            return c;
      }
      private ProtectionDomain preDefineClass(String name, ProtectionDomain pd) {
            if (!checkName(name))
                throw new NoClassDefFoundError("IllegalName: " + name);
    
            // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
            // relies on the fact that spoofing is impossible if a class has a name
            // of the form "java.*"
            if ((name != null) && name.startsWith("java.")) {
                throw new SecurityException
                    ("Prohibited package name: " +
                     name.substring(0, name.lastIndexOf('.')));
            }
            if (pd == null) {
                pd = defaultDomain;
            }
    
            if (name != null) checkCerts(name, pd.getCodeSource());
    
            return pd;
        }
    }
    

    8.sun.misc.Launcher类

    该类初始化时主要执行以下动作:
    1)初始化扩展类加载器,parentClassLoader为null(因为启动类加载器由c++实现);
    2)初始化应用类加载器,并设其parentClassLoader为扩展类加载器;
    3)设置sun.misc.Launcher#loader为应用类加载器;(java.lang.ClassLoader#getSystemClassLoader返回的就是该字段)
    4)设置当前线程的ContextClassLoader为应用类加载器;

    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);
    
            // Finally, install a security manager if requested
            String s = System.getProperty("java.security.manager");
            if (s != null) {
                // init FileSystem machinery before SecurityManager installation
                sun.nio.fs.DefaultFileSystemProvider.create();
    
                SecurityManager sm = null;
                if ("".equals(s) || "default".equals(s)) {
                    sm = new java.lang.SecurityManager();
                } else {
                    try {
                        sm = (SecurityManager)loader.loadClass(s).newInstance();
                    } catch (IllegalAccessException e) {
                    } catch (InstantiationException e) {
                    } catch (ClassNotFoundException e) {
                    } catch (ClassCastException e) {
                    }
                }
                if (sm != null) {
                    System.setSecurityManager(sm);
                } else {
                    throw new InternalError(
                        "Could not create SecurityManager: " + s);
                }
            }
        
    

    相关文章

      网友评论

          本文标题:一、类加载机制

          本文链接:https://www.haomeiwen.com/subject/jmflahtx.html