优点:
提高 Java 程序的灵巧性可以做得到程序启动运行的时候动态拓展加载其它 class 文件缺陷:
类加载会带来一定的性能开销类加载事理类的生命周期验证、准备、解析我们统称为:连接(Lingking)加载、验证、准备、初始化、卸载这 5 个阶段的实行顺序是固定的解析在分外情形下可能在初始化之后实行,由于 Java 支持运行时绑定类的初始化机遇
Java 虚拟机并没有逼迫约束在什么时候进行类的初始化,这个虚拟机可以自由把握。但是在 Java 虚拟机规范中,有 5 种情形必须立即对类进行初始化(在此之前的几个阶段必须须要完成):
当实行 new、getstatic、putstatic、invokestatic 4 个指令的时候,如果类没有被初始化,则须要先触发类的初始化。利用 Java 发射进行创建类的时候,如果类没有被初始化,则须要先触发类的初始化。初始化类的时候父类没有进行初始化,则须要先触发父类的初始化。虚拟机启动的时候指定的实行主类,虚拟机会优先初始化这个类。如果你还是利用的 JDK 1.7 版本,方法句柄对应的类没有进行初始化,则须要先触发类的初始化。类的加载过程下面为大家详细讲解类加载的 5 个阶段详细的操作,详细的细节大家可以阅读周老师的《深入理解 Java 虚拟机:JVM 高等特性与最佳实践》。
加载“加载”只是“类加载”的一个主要过程,内存中会存在一个 Class 工具,作为方法去中的数据访问入口。类加载器分为系统类加载器、自定义类加载,开拓者可以通过自定义的类加载器去掌握字节流的获取当时,重写类加载器的 loadClass() 方法。
常见的加载办法:
从压缩包中读取 class 文件,例如:JAR、WAR 等格式。可以从网络中获取,常见的是 URLClassLoader 可以通过网络获取 class 资源。程序运行的时候通过动态代理技能可以成功代理类(ProxyGenerateProxyClass)。其它外部文件,通过 JSP 技能天生对应的 class。也可以从数据库中读取 class。验证验证是 Java 虚拟机对系统的一种保护机制,避免加载了危险的字节流直接导致程序涌现问题,验证可以提高 Java 程序的安全性。验证统共分为四个主要的阶段:
文件格式的验证:只有通过了文件格式的校验,字节流才会进入内存的方法区中存储。元数据的验证:紧张对元数据的语义检讨,严格按照 Java 语义规范实行。字节码的验证:紧张是验证源码字节的类型、是否合法、是否存在危险等方面进行验证。符号引用的验证:符号验证例如查找常量池中的符号数据、通过全量限定名查找对应的类等等,紧张保障程序能解析正常运行。准备准备阶段紧张是为加载的变量、常量分配内存空间,这里不包括实例化工具的变量。
解析
将常量池中的符号引用更换为直接引用(内存地址)的过程。
初始化紧张是给静态常量赋值,赋值紧张有两种办法:
定义的变量赋值初始值静态代码块里面的静态常量赋值类加载器不同的类加载加载的想通类名的类一定是不相等的,只有同一个类加载器加载的类之间进行比拟才会故意义,这点才开拓中一定要服膺。
学习类加载器一定须要搞清楚双亲委派机制:
类加载器分为:
启动(Bootstrap)类加载器:紧张是加载 JAVA_HOME>\lib 目录中的文件,例如 rt.jar.C 措辞实现,Java 中无法获取到改加载器的引用,可通过 System.getProperty("sun.boot.class.path") 进行查看扩展(Extension)类加载器:扩展类加载器是由 Sun 的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它卖力将 JAVA_HOME /lib/ext 或者由系统变量 -Djava.ext.dir 指定位置中的类库加载到内存中。开拓者可以直策应用标准扩展类加载器系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它卖力将用户类路径(java -classpath 或 -Djava.class.path)变量所指的目录,即当前类所在路径及其引用的第三方类库的路径。自定义类加载器:extends ClassLoader 类,重写 findClass 方法。类加载器源码阐发核心代码范例的双亲委派机制,首选判断类是否被加载,如果没有加载委托给父类加载或者委派给启动类加载器加载。
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 { // 由于启动类加载器无法被 Java 程序直接引用,因此默认用 null 替代 // parent == null 就意味着由启动类加载器考试测验加载该类, // 即通过调用 native 方法 findBootstrapClass0(String name)加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 父累加器没有加载到,那么加载自定义类加载器,如果到了这层还是弗成则 NotFindClass 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; } }
真实案例
培植:低代码平台的过程中,如何对标准业务进行二次开拓呢?租户定制化的差异化代码如何集成到标准的业务中?通过类加载器的机制:
在项目启动的时候在自定义的 ApplicationRunner 中,通过自定义类加载器加载远程类文件。系统当前类加载设置为自定义类加载器的父类,当父类无法找到到某个类的时候,会去自定义类加载器查找。由于项目 MyBatis XML 是动态加载的,方便租户拓展以是须要设置 MyBatis 的类加载器 Resources.setDefaultClassLoader(definitionLoader);。// mybatis xml 类加器源码public Class<?> classForName(String name, ClassLoader classLoader) throws ClassNotFoundException { return classForName(name, getClassLoaders(classLoader));}// mybatis 类加载器的顺序,预留了 defaultClassLoader 用于自定义累加器ClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader}; }// 遍历类加载器,挨个查找类文件Class<?> classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException { for (ClassLoader cl : classLoader) { if (null != cl) { try { Class<?> c = Class.forName(name, true, cl); if (null != c) { return c; } } catch (ClassNotFoundException e) { // we'll ignore this until all classloaders fail to locate the class } } } throw new ClassNotFoundException("Cannot find class: " + name); }
上述大略先容项目中的类加载器的利用案例,紧张涉及加载外部文件,设置系统当前的类加载器为自定义类加载器的父类,如果涉及其它框架(由于很多框架都有自己的一套类加载器),则须要设置自定义类加载器的关系,否则存在 NotFindClass。