java class文件的加载

574 阅读5分钟

JVM加载Class文件主要分3个过程:Loading 、Linking、Initialzing

1. Loading

这个过程主要是将class文件加载到内存中,主要理解双亲委派模型和ClassLoader

  1. 类加载器

    JDK提供的三个ClassLoader为Launcher的内部类。相关截图都在sun.misc.Launcher中

    BootClassLoader 加载范围sun.boot.class.path image.png ExtClassLoader 加载范围java.ext.dirs
    image.png AppClassLoader 加载范围java.class.path image.png CustomClassLoader 可自定义加载范围

  2. 双亲委派模型

    graph BT
    A(.class)-->B(CustomClassLoader)--加载过-->Z((返回结果))
    B(CustomClassLoader)--未加载过-->C(AppClassLoader)--加载过-->Z((返回结果))
    C(AppClassLoader)--未加载过-->D(ExtClassLoader)--加载过-->Z((返回结果))
    D(ExtClassLoader)--未加载过-->E(BootClassLoader)--加载过-->Z((返回结果))
    E(BootClassLoader)==未加载过,自己可以加载===>Z((返回结果))
    E(BootClassLoader)==自己不可以加载==>D(ExtClassLoader)
    D(ExtClassLoader)==自己可以加载===>Z((返回结果))
    D(ExtClassLoader)==自己不可以加载==>C(AppClassLoader)
    C(AppClassLoader)==自己可以加载===>Z((返回结果))
    C(AppClassLoader)==自己不可以加载===>B(CustomClassLoader)
    B(CustomClassLoader)==自己可以加载===>Z((返回结果))
    B(CustomClassLoader)==自己不可以加载===>异常((ClassNotFoundException))
    

    1、防止重复加载同一个.class,通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据 安全。
    2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加 载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对 象。这样保证了Class执行安全。

  3. 双亲委派源码体现

 /**
   * 从调用方式上看class文件的加载类似递归加载。
   * ClassLoader采用的是模板方法的设计模式,只需要重写findClass()方法就能实现自己的ClassLoader
   */
  protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
          synchronized (getClassLoadingLock(name)) {
              //检查class文件是否加载过,如果加载过就返回,如果没有加载继续加载
              Class<?> c = findLoadedClass(name);
              if (c == null) {
                  long t0 = System.nanoTime();
                  try {
                      if (parent != null) {
                          //parent中加载,由继续执行loadClass方法,有就返回
                          c = parent.loadClass(name, false);
                      } else {
                          //如果没有parent就由BootClassLoader加载
                          c = findBootstrapClassOrNull(name);
                      }
                  } catch (ClassNotFoundException e) {
                  }

                  if (c == null) {
                      //如果当前ClassLoader没有找到,就去加载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;
          }
      }
  1. 自定义ClassLoader
    public class CustomClassLoader extends ClassLoader{
        //
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException{
            try {
                File file = new File(name);
                FileInputStream fis = new FileInputStream(file);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

                int len;
                byte[] buffer = new byte[1024];
                while ((len = fis.read(buffer)) != -1) {
                    byteArrayOutputStream.write(buffer, 0, len);
                }
                byte[] bytes = byteArrayOutputStream.toByteArray();
                Class<?> clazz = defineClass(null, bytes, 0, bytes.length);
                System.out.println(clazz);
                return clazz;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Test
        public void classLoaderTest() throws Exception{
            CustomClassLoader customClassLoader = new CustomClassLoader();
            Class<?> clazz = customClassLoader.loadClass("D:\\zyouke_git\\zyouke\\java_base\\out\\production\\java_base\\spi\\IHelloSpi.class");
            System.out.println(clazz);
        }
    }
  1. 如何破坏双亲委派模型
    1. 自定义ClassLoader重写loadClass()
    2. 自定义ClassLoader,在构造方法中调用super(parent)指定parent
2. Linking

Linking链接的过程分3个阶段:Vertification、Preparation、Resolution。
Vertification: 验证Class文件是否符合JVM规定。
Preparation:给静态成员变量赋默认值。
Resolution:将类、方法、属性等符号引用解释为直接引用;常量池中的各种符号引用解释为指针、偏移量等内存地址的直接引用

3. Initialzing

调用初始化代码clint,给静态成员变量赋初始值。 这里可以了解下必须初始化的5种情况:
1,new getstatic putstatic invokestatic指令,访问final变量除外。
2,java.lang.reflect对类进行反射调用时。
3,初始化子类的时候,父类必须初始化。
4,虚拟机启动时,被执行的主类必须初始化。
5,动态语言支持java.lang.invoke.MethodHandler解释的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化。

扩展之tomcat是如何打破双亲委派模型,为什么要打破双亲委派模型
  1. URLClassLoader
    commonLoader,catalinaLoader,sharedLoader都是基于URLClassLoader创建的。

    public static void main(String[] args) throws Exception{
        // 创建一个url数组,数组里面的元素是从maven库中找的jar
         URL[] urlArr = new URL[]{new URL("file:C:/Users/Administrator/.m2/repository/org/objenesis/objenesis/2.2/objenesis-2.2.jar")};
         URLClassLoader urlClassLoader = new URLClassLoader(urlArr,Thread.currentThread().getContextClassLoader());
         // org.objenesis.ObjenesisBase是这个jar下的class
         Class<?> clazz = urlClassLoader.loadClass("org.objenesis.ObjenesisBase");
         System.out.println(clazz.getClassLoader());
         System.out.println(clazz);
    }
      //打印结果
     //加载类的classLoader java.net.URLClassLoader@e2d56bf
     //class org.objenesis.ObjenesisBase
    
  2. tomcat自定义commonLoader,catalinaLoader,sharedLoader

      //org.apache.catalina.startup.Bootstrap#initClassLoaders
      //在tomcat启动时会调用
      private void initClassLoaders() {
         try {
             /**
              * 创建commonLoader,会使用catalina.properties配置文件中
              * common.loader配置的jar构建URLClassLoader返回,parent传的null时,在后续的逻辑              * 中其实使用的是java.lang.ClassLoader#getSystemClassLoader()返回的classLoader
              * 作为parent
              */
             commonLoader = createClassLoader("common", null);
             if( commonLoader == null ) {
                 commonLoader=this.getClass().getClassLoader();
             }
             /**
              * catalinaLoader,会使用catalina.properties配置文件中
              * cserver.loader配置的jar构建URLClassLoader返回,parent使用的是commonLoader
              */
             catalinaLoader = createClassLoader("server", commonLoader);
             /**
              * catalinaLoader,会使用catalina.properties配置文件中
              * shared.loader配置的jar构建URLClassLoader返回,parent使用的是commonLoader
              */
             sharedLoader = createClassLoader("shared", commonLoader);
         } catch (Throwable t) {
             handleThrowable(t);
             log.error("Class loader creation threw exception", t);
             System.exit(1);
         }
      }
    
    
  3. WebappClassLoader

    一个 Tomcat 可能会部署多个这样的 web 应用,不同的 web 应用可能会依赖同一个第三方库的不同版本, 为了保证每个 web 应用的类库都是独立的,需要实现类隔离。而Tomcat 的自定义类加载器 WebAppClassLoader 解决了这个问题,每一个 web 应用都会对应一个 WebAppClassLoader 实例,不同的类加载器实例加载的类是不同的,Web应用之间通各自的类加载器相互隔离。

    在其父类WebappClassLoaderBase重写了ClassLoaderfindClass和loadClass方法

       //省略部分代码对关键部分解释
       public Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clazz = null;
        try {
            try {
                //先在 Web 应用目录下查找类
                clazz = findClassInternal(name);
            } catch(AccessControlException ace) {
                throw new ClassNotFoundException(name, ace);
            } catch (RuntimeException e) {
                throw e;
            }
            if ((clazz == null) && hasExternalRepositories) {
                try {
                    //在 Web 应用目录下查找不到类,交给父类加载
                    clazz = super.findClass(name);
                } catch(AccessControlException ace) {
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    throw e;
                }
            }
            //父类加载不到就抛异常
            if (clazz == null) {
                throw new ClassNotFoundException(name);
            }
        } catch (ClassNotFoundException e) {
            throw e;
        }
    
    }
    
    
       public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
        synchronized (getClassLoadingLock(name)) {
            // (0) 检查本地缓存是否有
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                return (clazz);
            }
    
            // (1) 从系统中去加载
            clazz = findLoadedClass(name);
            if (clazz != null) {
                return (clazz);
            }
            String resourceName = binaryNameToPath(name, false);
            // 获取java的类加载器,并加载
            ClassLoader javaseLoader = getJavaseClassLoader();
            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                }
            }
            try {
                // 尝试在本地目录搜索 class 并加载
                clazz = findClass(name);
                if (clazz != null) {
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                
            }
        //都找不到抛出异常
        throw new ClassNotFoundException(name);
    }