zoukankan      html  css  js  c++  java
  • 类加载机制

    一、类冲突测试

         写了两个类路径完全一样的类,然后分别打包成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、运行时期

       

        

  • 相关阅读:
    MFC常用控件使用方法
    用CImage类来显示PNG、JPG等图片
    javascript
    gSoap学习笔记
    Linux增加Swap分区
    nagios 监控shell脚本
    新机器连接老机器遇到的ERROR
    linux下PS1命令提示符设置
    python基础篇之进阶
    mysql不能使用localhost登录
  • 原文地址:https://www.cnblogs.com/YDDMAX/p/5534139.html
Copyright © 2011-2022 走看看