类加载机制

一、类冲突测试

     写了两个类路径完全一样的类,然后分别打包成a.jar和b.jar。在另一个project里面同时依赖这两个jar包,eclipse不会报错,编译和运行也不会报错。而JVM真正载入的类是a.jar的类(JVM应该会根据jar的名称顺序来载入类吧)。

二、jar包结构

     1、导出runnable jar file  

         此时导出的类包含该project包含的所有类(pom里面的类,buildpath里面设置的类)

         包结构如下图:

        

    2、导出非runnable jar(现在使用maven开发,使用的都是这种形式)

       此时导出的只有本工程自己的class文件,只有pom文件,没有依赖的class文件。

      保包结构如下图:

          

 3、war包结构分析

      一个webapp下的目录结构如下图。

     

      WEB-INF的结构如下图。其中classes文件是自己的class文件,lib是依赖的外部类。还需要注意web.xml也在该目录下。

      

 三、系统类加载器

          可以通过静态方法ClassLoader.getSystemClassLoader()获得,获得的ClassLoader可以认为是单例的

          该类加载器在父类加载器加载不到类的时候,在java.class.path下查找类

四、当前类加载器自动加载和指定类加载器加载

    1、什么是当前类加载器自动加载?   不是程序使用specificClassloader.loadClass(String)来设置类加载器来加载

         Object a=new Object();

         Class.forName(String); //和上面的含义一样

    2、指定类加载器  (需要打破当前类加载器的限制)

         1、线程上下文类加载器

         2、通过新建或者object.getClassLoader()获得类加载器来加载类(classloader.loadClass())

五、Class.forName(String)

     源码如下。红色字体说明Class.forName实际上是使用当前类加载器进行加载。实现里面最后调用了native方法。

   @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

   下面是JVM的执行过程分析:

      1、系统字典(包含3列,根据类的全路径和类的定义加载器可以通过hash的方法找到对应的Class实例)

            

       2、执行过程(图)

                    

   3、执行过程

步骤说明
1 调用Class.forName(className)方法,该方法会调用native的JVM实现,调用前该方法会确定准备好需要加载的类名以及ClassLoader,将其传递给native方法
2 进入到JVM实现后,首先会在SystemDictionary中根据类名和ClassLoader组成hash,进行查询,如果能够命中,则返回
3 如果加载到则返回
4 如果在SystemDictionary中无法命中,将会调用Java代码:ClassLoader.loadClass(类名),这一步将委派给Java代码,让传递的ClassLoader进行类型加载
5 以URLClassLoader为例,ClassLoader确定了类文件的字节流,但是该字节流如何按照规范生成Class对象,这个过程在Java代码中是没有体现的,其实也就是要求调用ClassLoader.defineClass(byte[])进行解析类型,该方法将会再次调用native方法,因为字节流对应Class对象的规范是定义在JVM实现中的
6 进入JVM实现,调用SystemDictionary的resolve_stream方法,接受byte[],使用ClassFileParser进行解析
7 SystemDictionary::define_instance_class
8 如果类型被加载了,将类名、ClassLoader和类型的实例引用添加到SystemDictionary中
9 返回
10 返回
11 从Java实现返回到Java代码的defineClass,返回Class对象
12 返回给loadClass(Classname)方法
13 返回给Java实现的SystemDictionary,因为在resolve_class中调用的ClassLoader.loadClass。这里会做出一个判断,如果加载Class的ClassLoader并非传递给resolve_class的ClassLoader,那么会将类名、传递给resolve_class的ClassLoader以及类型的实例引用添加到SystemDictionary中
14 返回给Class.forName类型实例

      上述的过程比较复杂,但是简化理解一下它所做的工作,我们将SystemDictionary记作缓存,Class.forName或者说Java默认的类型加载过程是:

  1. 首先根据ClassLoader,我们称之为initialClassLoader和类名查找缓存,如果缓存有,则返回;
  2. 如果缓存没有,则调用ClassLoader.loadClass(类名),加载到类型后,保存<类名,真实加载类的ClassLoader,类型引用>到缓存,这里真实加载类的ClassLoader我们可以叫做defineClassLoader;
  3. 返回的类型在交给Java之前,将会判断defineClassLoader是否等于initialClassLoader,如果不等,则新增<类名,initialClassLoader,类型引用>到缓存。这里区分initialClassLoader和defineClassLoader的原因在于,调用initialClassLoader的loadClass,可能最终委派给其他的ClassLoader进行了加载。

 六、ClassLoader.loadClass(String)

        可以指定类加载器加载类,该方法的源码实现了双亲委托机制。

       相对于Class.forName()该过程开始于第4步,没有前3步,该过程简单说就是:调用ClassLoader.loadClass(类名),加载到类型后,保存<类名,真实加载类的ClassLoader,类型引用>到缓存,这里真实加载类的ClassLoader我们可以叫做

       defineClassLoader。也就是,调用ClassLoader.loadClass(类名)之后,并不一定会在缓存中生成一条<类名,ClassLoader,类型引用>的记录,但是一定会生成一条<类名,真实加载类的ClassLoader,类型引用>的记录。

        该方法首先调用下面描述的方法,查询系统字典看是否能查到,如果系统字典里面没有然后走双亲委托制查询和加载。

七、ClassLoader.findLoadedClass(String className)

         1、 该方法是protected final修饰的方法,也就是ClassLoader的子类可以内部使用,但是无法通过ClassLoader.findLoadedClass直接调用。

         2、该方法从SystemDictionary中获取的,当调用ClassLoader.findLoadedClass(className)时,会到SystemDictionary中以className和ClassLoader为key,进行查询,如果命中,则返回类型实例。 

 八、J2EE的类加载器

          Tomcat_WebappClassLoader_loadClass

九、几种常见的类加载错误

      NoClassDefFoundError(类加载器找不到类)(可能的一种情况是:pom的scope为provided,但是web服务器又找不到时会报此错误)

      NoSuchMethodError(jar包冲突,因为编译依赖的jar包和真正执行时加载的jar包不一样。本质是没有记载到正确的类)

      ClassCastException(多余一个类被加载,ClassLoader定义了命名空间。)

      LinkageError (还需要研究下)(多余一个类被加载)

十、maven的编译和打包以及程序的运行

     1、maven可以进行包的依赖管理和编译以及打包

          编译时首先编译依赖的子project,然后再编译父project,以此递推。

         各个子project在编译和打包的过程中,所依赖的jar的优先级由自己的pom文件定义,而不是父pom。          

   2、在出现类加载的错误时,应该考虑下面两个过程来定位和解决问题

     1、编译时期

     2、运行时期

   

    

原文地址:https://www.cnblogs.com/YDDMAX/p/5534139.html