源码层面剖析类加载机制

126 阅读6分钟

通过java命令执行代码的大体流程如下:

  • windows系统下,java.exe调用底层的jvm.dll文件创建java虚拟机(C++实现)
  • 创建一个引导类加载器(C++实现)
  • C++调用java代码,创建JVM启动器实例sun.misc.Launcher。该类由引导类加载器负责加载创建其他类加载器
  • 调用getLauncher()获取Launcher实例
  • 通过Launcher.getClassLoader()获取AppClassLoader()
  • 通过classloader的loadclass("类的全限定名")加载类

类被加载到方法区中后主要包含运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。

类加载器的引用:这个类到类加载器实例的引用

对应class实例的引用类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。

类加载器和双亲委派机制

上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器。

  • 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等。
  • 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包。
  • 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类。
  • 自定义加载器:负责加载用户自定义路径下的类包。

注意:

sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。

在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。

JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序

private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;

public static Launcher getLauncher() {
    return launcher;
}
//Launcher 构造方法
public Launcher() {
    ExtClassLoader var1;
    try {
        //创建扩展类加载器
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        //创建应用程序类加载器,并赋值给this.loader,getClassLoader方法返回的就是this.classloader
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    if (var2 != null) {
        SecurityManager var3 = null;
        if (!"".equals(var2) && !"default".equals(var2)) {
            try {
                var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
            } catch (IllegalAccessException var5) {
            } catch (InstantiationException var6) {
            } catch (ClassNotFoundException var7) {
            } catch (ClassCastException var8) {
            }
        } else {
            var3 = new SecurityManager();
        }

        if (var3 == null) {
            throw new InternalError("Could not create SecurityManager: " + var2);
        }

        System.setSecurityManager(var3);
    }

}
//getClassLoader方法
public ClassLoader getClassLoader() {
    return this.loader;
}

双亲委派机制

image.png

加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。

我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:

  1. 首先检查一下指定名称的类是否已经加载过,如果已经加载过了,就不需要再加载了,直接返回

  2. 如果没有加载,如果父加载器不等于null,则去执行父加载器的loadclass方法。如果父类为null,说明父加载器为引导类加载器,则会调用引导类加载器加载

  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

//ClassLoader类的loadclass方法
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) {
                    //如果parent不为null,则调用父加载器的loadclass方法
                    c = parent.loadClass(name, false);
                } else {
                //若为null则调用引导类加载器去加载类 即如果当前为extclassloader则去调用引导类加载器加载,因为extclassloader父加载器是引导类加载器 
                //但因为引导类加载器是c++源码无法赋值,所以赋值为null
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            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;
    }
}

为什么要设计双亲委派机制?

  • 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
  • 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

全盘负责委托机制

“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,否则通常该类

所依赖及引用的类也由这个ClassLoder载入。

自定义类加载器示例:

自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。

public class MyClassLoaderTest {
   static class MyClassLoader extends ClassLoader {
      private String classPath;
      public MyClassLoader(String classPath) {
         this.classPath = classPath;
      }
      private byte[] loadByte(String name) throws Exception {
         name = name.replaceAll("\.", "/");
         FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
         int len = fis.available();
         byte[] data = new byte[len];
         fis.read(data);
         fis.close();
         return data;
      }
      protected Class<?> findClass(String name) throws ClassNotFoundException {
         try {
            byte[] data = loadByte(name);
            //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
            return defineClass(name, data, 0, data.length);
         } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
         }
      }
   }
   public static void main(String args[]) throws Exception {
      //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
      MyClassLoader classLoader = new MyClassLoader("D:/test");
      //D盘创建 test/com/cui/jvm 几级目录,将User类的复制类User1.class丢入该目录
      Class clazz = classLoader.loadClass("com.cui.jvm.User1");
      Object obj = clazz.newInstance();
      Method method = clazz.getDeclaredMethod("sout", null);
      method.invoke(obj, null);
      System.out.println(clazz.getClassLoader().getClass().getName());
   }
}
运行结果: 
=======自己的加载器加载类调用方法======= 
com.cui.jvm.MyClassLoaderTest$MyClassLoader

打破双亲委派机制

下面为打破沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载我们自己实现java.lang.String.class

关键:重写类加载方法,实现自己的加载逻辑,不委派给双亲加载

public class MyClassLoaderTest {
   static class MyClassLoader extends ClassLoader {
      private String classPath;

      public MyClassLoader(String classPath) {
         this.classPath = classPath;
      }

      private byte[] loadByte(String name) throws Exception {
         name = name.replaceAll("\.", "/");
         FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
         int len = fis.available();
         byte[] data = new byte[len];
         fis.read(data);
         fis.close();
         return data;
      }

      protected Class<?> findClass(String name) throws ClassNotFoundException {
         try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
         } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
         }
      }

      /**
       * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
       *
       * @param name
       * @param resolve
       * @return
       * @throws ClassNotFoundException
       */
      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) {
               // 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.getFindClassTime().addElapsedTimeFrom(t1);
               sun.misc.PerfCounter.getFindClasses().increment();
            }
            if (resolve) {
               resolveClass(c);
            }
            return c;
         }
      }
   }

   public static void main(String args[]) throws Exception {
      MyClassLoader classLoader = new MyClassLoader("D:/test");
      //尝试用自己改写类加载机制去加载自己写的java.lang.String.class
      Class clazz = classLoader.loadClass("java.lang.String");
      Object obj = clazz.newInstance();
      Method method = clazz.getDeclaredMethod("sout", null);
      method.invoke(obj, null);
      System.out.println(clazz.getClassLoader().getClass().getName());
   }
}