乘风原创程序

  • Java类加载机制
  • 2021/2/23 10:11:18
  • JVM类加载机制


    前言

    java类加载机制个人笔记(如有意见,请各位前辈多多指教)


    一、JVM类加载全过程

    运行流程:加载–>>验证–>>准备–>>解析–>>初始化。

    二、流程详细解析

    1. 加载:查找文件通过IO读取字节码文件(懒加载)。
    2. 验证:校验这些字节码内容是否符合JVM的规范。
    3. 准备:给类中静态变量赋初始值并且分配内存
    4. 解析:将符号引用替换为直接引用
      符号引用:可以通过javap -v Math.class命令来查看,可以将方法名当做符号,符号存储在常量池中。
      直接引用:把一些静态方法替换为指向数据所存内存的指针也就是静态链接的过程。在类加载过程中完成。
    5. 初始化:初始化是类加载的最后一步,在这里静态变量会被赋予初值,执行静态方法区。

    三、类加载器和双亲委派机制

    1.类加载器

    1. 启动类加载器:负责加载JVM运行的,位于JRE的lib目录下的核心类库,比如rt.jar、charesets.jar等。
    2. 扩展类加载器:负责加载支撑 JVM运行的位于jre的lib目录下的ext扩展目录中的jar包。
    3. 应用程序类加载器:负责加载ClassPath路径下的类包,也就是自己写的类。
    4. 自定义加载器:负责加载用户自定义路径下的类包。
      自定义加载器:继承ClassLoader类并且实现findClass方法默认实现抛出异常

    自定义加载器(示例代码):

    import java.io.FileInputStream;
    import java.lang.reflect.Method;
    
    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 {
            MyClassLoader classLoader = new MyClassLoader("D:/test");
            Class clazz = classLoader.loadClass("com.zzj.jvm.User1");
            Object obj = clazz.newInstance();
            Method method = clazz.getDeclaredMethod("sout", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader().getClass().getName());
        }
    }
    

    2.双亲委派机制

    1. 类加载器关系介绍:
      应用程序类加载器–>>扩展类加载器–>>启动类加载器
      备注:启动类加载器为最顶层加载器以此类推父子关系,父加载器加载失败由子加载器自己加载
    2. 为什么要设计双亲委派机制?
      沙箱安全机制: 自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
      避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
    3. 打破双亲委派机制
      我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:
      ①:一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
      ②:部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
      ③:web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
      ④:web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。

      tomcat几个主要加载器
      commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问
      catalinaLoader:Tomcat容器私有的类加载器,加载路径中的clas对webapp不可见.
      sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见但对于Tomcat容器不可见
      WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
      如何打破双亲委派机制(在自定义类加载器中覆盖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) {
                        // 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;
                }
            }
    

    本文地址:https://blog.csdn.net/weixin_44499047/article/details/112366302