11.从JDK源码级别彻底剖析JVM类加载机制

247 阅读3分钟

1、从java.exe开始讲透Java类加载运行全过程 2、从JDK源码级别剖析JVM核心类加载器 3、从JDK源码级别剖析类加载双亲委派机制 4、手写自定义类加载器打破双亲委派机制 5、Tomcat类加载机制深度剖析 6、手写Tomcat类加载器实现多版本代码共存隔离

类加载运行全过程

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

  • 创建Java虚拟机 + 创建一个引导类加载器实例(c++实现)
  • c++调用Java代码创建JVM启动器实例sun.misc.Launcher
  • sun.misc.Launcher.getLauncher()获取运行类自己的ClassLoader
  • classLoader.loadClass("com.*.类名")
  • 类名.main()

其中loadClass的类加载过程有如下几步

加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载

#javap命令是JDK提供的一个工具,用于对Java类进行反编译。
javap -v .\User.class

主类在运行过程中如果使用到其它类,会逐步加载这些类。

jar包或war包里的类不是一次性全部加载的,是使用到时才加载。

类加载器和双亲委派机制

Java里有如下几种类加载器(jdk8)

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

双亲委派机制

双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载

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

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

自定义类加载器示例


public class MyClassLoaderTest {

    static class MyClassLoader extends ClassLoader {

        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (!name.startsWith("com.lywtimer")) {
                // 首先委托给父类加载器尝试加载
                try {
                    return super.loadClass(name);
                } catch (ClassNotFoundException e) {
                    // 如果父类加载器无法加载,则自己尝试加载
                }

                // 如果找不到类文件资源,则抛出ClassNotFoundException
                if (findClass(name) == null) {
                    throw new ClassNotFoundException(name);
                }
            }

            byte[] data;
            try {
                data = loadByte(name);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
            //否则调用defineClass将class文件字节码解析成JVM可以执行的Java类
            return defineClass(name, data, 0, data.length);
        }

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

    }

    public static void main(String[] args) throws ClassNotFoundException, MalformedURLException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        ClassLoader classloader1 = ClassLoader.getSystemClassLoader();
        System.out.println("默认的系统类加载器:" + classloader1);
        ClassLoader classloader2 = null;
        try {
            classloader2 = Class.forName("java.lang.Object").getClassLoader();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        System.out.println("java.lang.Object加载器:" + classloader2);
        ClassLoader classloader3 =MyClassLoaderTest.class.getClassLoader();
        System.out.println(MyClassLoaderTest.class + "加载器:" + classloader3);


        //
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        Class clazz = classLoader.loadClass("com.lywtimer.test.User");
        System.out.println(clazz.getClassLoader());
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("info", null);
        method.invoke(obj, null);
    }
}