美文网首页
Java类加载器

Java类加载器

作者: 青岚之峰 | 来源:发表于2018-02-02 17:05 被阅读0次

    本文和大家聊聊Java类加载器这档子事。

    什么是类加载器?

    咱们先来给他下一个通俗点的定义:

    将java字节码(.class文件)转换成类对象(java.lang.Class),加载到JVM内存。

    Java中有哪几种类加载器?

    类加载器可分为四种:
    Bootstrap ClassLoader:由C++代码实现,加载sun.boot.class.path所指定的目录,或<JAVA_HOME>\lib目录中文件。

    Ext ClassLoader:加载系统变量 java.ext.dirs所指定的目录,或<JAVA_HOME>\lib\ext 目录中的文件。

    App ClassLoader:加载用户类路径下的文件。

    Custom ClassLoader:开发者自己折腾的类加载器,按需进行类文件加载。(例如:网络类加载器,通过网络加载.class文件)

    类加载器如何工作?

    第一步,类加载器向JVM查询目标对象(请求加载的对象)是否已经加载过,若已经加载过,则直接返回此对象;否则,进行第二步。

    第二步,获得当前类加载器的父类加载器,递归执行这一步骤,由下往上,直至到达顶级父类加载器(即Bootstrap ClassLoader),进行第三步。

    第三步,从顶级父类加载器,由上而下,在自己的搜索范围内检索目标对象的类文件(.class),若检索到,则读取该文件的字节码转换成Class对象到JVM,返回目标对象;否则,抛出ClassNotFoundException。

    有读者老爷说了,听你白扯了半天,对类加载器的认知还是不清楚,有没有实质性的东西?下面咱们来点实的。

    类加载器的委派模式

    从上文中类加载器的工作步骤,可以看出,递归获取父类加载器,由下往上,直达顶级父类加载器,在从顶级父类加载器,自上而下,逐级在自己的搜索范围内检索目标对象类文件,转换成Class对象(若检索到)的这种行为,称为类加载器的委派模式

    描述递归获取父类加载器,由下往上,直达顶级父类加载器的代码如下:

    //代码段截取自ClassLoader.loadClass(String name, boolean resolve)
     if (c == null) {
            long t0 = System.nanoTime();
            try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);//递归获取父类加载器
                  } else {
                      c = findBootstrapClassOrNull(name);//顶级父类加载器
                  }
               } catch (ClassNotFoundException e) {
                      // ClassNotFoundException thrown if class not found
                      // from the non-null parent class loader
               }
    

    描述从顶级父类加载器,自上而下,逐级在自己的搜索范围内检索目标对象类文件,转换成Class对象的代码如下:

     //代码段截取自ClassLoader.loadClass(String name, boolean resolve)
       if (c == null) {
          // If still not found, then invoke findClass in order
          // to find the class.
          long t1 = System.nanoTime();
          c = findClass(name);//在自己的搜索范围内进行检索,若找到,转换成Class对象
           .....................................
           .....................................
          }
          if (resolve) {
                resolveClass(c);
           }
           return c;
    

    真正实现类加载的是defineClass方法,代码如下:

    //代码段截取自URLClassLoader.findClass(final String name)
    String path = name.replace('.', '/').concat(".class");
    Resource res = ucp.getResource(path, false);
    if (res != null) {
       try {
              return defineClass(name, res); //执行类加载工作
       } catch (IOException e) {
             throw new ClassNotFoundException(name, e);
       }
    } else {
           return null;
    }
    

    回顾类加载器的委派模式,我们发现,发起类加载请求的类加载器,不一定会最终执行类的加载操作,可能其父类加载器来执行类加载。

    发起类加载请求的类加载器称为初始类加载器;完成类加载操作的类加载器称为定义类加载器。(为啥叫定义类加载?因名而得呗-defineClass)

    有读者老爷发问了,为什么要设计这种委派模式

    委派模式的作用

    个人理解,设计这种委派模式的用意有两点:
    第一,避免对象的重复加载
    第二,保护Java基础类型的行为

    关于第一点,自然不必多说。
    对于第二点,咱们设想一下,在没有委派模式的场景中,用户自己编写了一个java.lang.String类,并将其放置在程式的类路径(ClassPath)中,那系统中就存在了多个不同的String类,这使得Java的基础类型的行为无法得到保证,程式也会变得很混乱。

    细心的读者可能会从上面的例子中发现,同一个类被两个不同的类加载器加载会出现问题,产生这种问题的原因是什么?在后面的类加载器的隔离性中会进行解释

    类加载器的层级结构是怎样的?

    如下是类加载器层级示意图:


    口说无凭,咱们来验证一下类加载器的层级结构,执行如下代码:

     package com.hys;
    
     public class Main {
    
         public static void main(String[] args) {
     
             ClassLoader c1 = User.class.getClassLoader();
             System.out.println(c1);
     
              ClassLoader c2 = c1.getParent();
              System.out.println(c2);         
    
              ClassLoader c3 = c2.getParent(); 
              System.out.println(c3); 
        }
    }
    

    执行结果:

    sun.misc.Launcher$AppClassLoader@b4aac2
    sun.misc.Launcher$ExtClassLoader@140e19d
    null 
    
    Process finished with exit code 0
    

    执行结果符合咱们的预期。

    有读者老爷发问了,怎么就符合预期了?结果中有个值为null,是个啥?Bootstrap ClassLoader呢?
    原因在于顶级类加载器是Bootstrap ClassLoader,此加载器是C++代码编写,所以,使用Java获取的时候,返回了null

    类加载器的隔离性

    JVM如何判断一个类的唯一性?
    JVM通过加载该类的类加载器和类名称来确定一个类的唯一性。

    这意味着我们使用两个不同的类加载器加载同一个类,而在JVM层面上却认为这是两个完全不同的类,从另一个层面看,相当于类被类加载器所包裹与另个类加载器中的类进行了隔离

    如何证明这种隔离的存在?执行如下代码:

    package com.hys;
    
    public class Main {
        public static void main(String[] args) {
    
            try{
               User u1 = new User();
               User u2 = new User();
               // u1和u2都是同一个类加载器加载的
               System.out.println("The same classloader: u1.isInstance(u2)" + u1.getClass().isInstance(u2));
    
                MyClassLoader c1 = new MyClassLoader();
                Class clazz = c1.loadClass("com.hys.User");
               // clazz是MyClassLoader类加载器加载的
                System.out.println("The different classloader: clazz.isInstance(u1)" + clazz.isInstance(u1));
            }catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }
    

    执行结果:

    The same classloader: u1.isInstance(u2)true
    The different classloader: clazz.isInstance(u1)false
    
    Process finished with exit code 0
    

    自定义类加载器

    自定义类加载器,一般是继承ClassLoader类,覆盖findClass(String name)方法,在此方法中做查询与读取.class文件的操作,如下代码:

    package com.hys;
    
    import java.io.*;
    
    public class MyClassLoader extends ClassLoader {
    
        private String mRootDir;
    
        public MyClassLoader() {
    
        }
    
        public MyClassLoader(String rootDir) {
            mRootDir = rootDir;
        }
    
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] data = loadClassData(name);
            if (data == null) {
                throw new ClassNotFoundException();
            }else{
                return defineClass(name, data, 0, data.length);
            }
        }
    
        private byte[]loadClassData(String className) {
            String path = classNameToPath(className);
            try {
                InputStream ins = new FileInputStream(path);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                while ((bytesNumRead = ins.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                return baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private String classNameToPath(String className) {
    
            String classPath;
            if(mRootDir == null || mRootDir.isEmpty())
                classPath = className.replace('.', File.separatorChar) + ".class";
            else
                classPath = mRootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
    
            return classPath;
        }
    }
    

    我是青岚之峰,如果读完后觉的有所收获,欢迎点赞加关注

    相关文章

      网友评论

          本文标题:Java类加载器

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