美文网首页
类加载的过程

类加载的过程

作者: 掩流年 | 来源:发表于2019-12-31 13:53 被阅读0次

    类从编译到执行的过程

    类从编译到执行的过程主要经历了以下几个步骤:
    例如加载ClassLoadTest.java文件
    1.编译器将ClassLoadTest.java源文件编译成ClassLoadTest.class字节码文件。
    2.ClassLoader将字节码文件转换为JVM中的Class<ClassLoadTest>对象。
    3.JVM利用Class<ClassLoadTest>对象实例化ClassLoadTest对象。

    看一个加载的例子

    Class aClass = ClassLoadTest.class; 
    
    ClassLoader classLoader = ClassLoadTest.class.getClassLoader();
    Class class3 = classLoader.loadClass("com.reflect.ClassLoadTest");
    

    如之上代码所示,其实本质上是等价的。在java虚拟机规范中讲明了这一点。

    If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).

    其中涉及到两个名词。我们称触发一个类加载的过程的类加载器叫做initiating loader,而最终加载这个类的类加载器叫做defining loader。例子:一个类加载器X触发了java.util.Map类的加载,但是它最终被系统的类加载器加载了(因为双亲委派模型),我们称X为initiating loader,系统的类加载器为defining loader。

    如果使用Idea中的ASM插件去查看字节码的话,会发现Class aClass = ClassLoadTest.class;这段代码执行了LDC指令,会去常量池中拿东西。我们假设这段代码是运行在Test类中的,这时候,ClassLoadTest.class的加载会被Test.class.classLoader作为initiating loader触发。
    ClassLoader classLoader = A.class.getClassLoader();其中的classLoader就是最终加载A.class的类加载器。
    我们令classLoader.loadClass("com.ClassLoadTest");就是令该类加载器加载ClassLoadTest.class

    什么是ClassLoader

    ClassLoader是Java中的核心组件,它主要是从系统外部读取class文件字节码,加载进JVM中,交由JVM进行连接初始化等。

    ClassLoader的种类

    • BootStrapClassLoader:C++编写的,加载核心库*.java
    • ExtClassLoader:Java编写,加载扩展库 javax.*
    • AppClassLoader:Java编写,加载程序所在的目录。
    • 自定义ClassLoader:Java编写,定制化加载
      所以自定义ClassLoader指的是自己定义编写类加载的规则,如何自己编写一个ClassLoader呢?
      java.lang.ClassLoader这个抽象类中,有一个方法:
        protected Class<? > findClass(String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }
    

    它的定义直接抛出异常,它的作用是提供给用户覆盖方法,编写自定义的ClassLoader。自定义编写ClassLoader的代码如下:

    public class MyClassLoader extends ClassLoader {
    
        private String fileName;
        private String path;
    
        public MyClassLoader(String fileName, String path) {
            this.fileName = fileName;
            this.path = path;
        }
    
        @Override
        public Class findClass(String name) {
            byte[] b = new byte[0];
            try {
                b = loadClassData(name, path);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return defineClass(name, b, 0, b.length);
        }
    
        private byte[] loadClassData(String name, String path) throws IOException {
            return Files.readAllBytes(Paths.get(path + name + ".class"));
    
        }
    
    }
    

    这样就完成了一个类加载器的编写。我们用写一个检查类进行验证

    class Check{
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
                InstantiationException {
            MyClassLoader myClassLoader = new MyClassLoader("LoadFile","\\mypath");
            Class c = myClassLoader.loadClass("LoadFile");
            System.out.println(c.getClassLoader());
            c.newInstance();
        }
    }
    

    打印的结果为

    com.reflect.MyClassLoader@7d4991ad
    load file ...
    

    类加载器的双亲委派机制

    双亲委派机制

    如上图所示,在开始类加载之前,类加载器首先会检查这个类曾经有没有被加载过。加载过则直接使用加载的Class对象,没有加载则向上委托给它的父加载器,检查父加载器有没有加载过。
    如果都没有加载过这个类,则会从上到下检查classpath中的jar,查找包中的jar是否有对应的类。
    使用双亲委派机制来加载类,主要是避免多份同样字节码的加载。

    loadClass和forName的区别

    类的装载过程主要有以下三步:

    • 加载
      通过ClassLoader加载class文件字节码,生成Class对象。
    • 链接
      1.校验:检查加载class的正确性和安全性
      2.准备:为类变量分配存储空间并设置类变量初始值
      3.解析:JVM将常量池的符号引用转换为直接引用
    • 初始化
      执行类变量赋值和静态代码块

    对于Class.forName和ClassLoad.loadClass而言,主要的区别是:
    Class.forName得到的class是完成初始化的。 ClassLoad.loadClass得到的class是没有链接的,也就是没有初始化的。
    这点可以在源代码中体现:
    forName的代码,可以看到它的返回方法中第二个参数置为true,则表明会被初始化。

        @CallerSensitive
        public static Class<?> forName(String className)
                    throws ClassNotFoundException {
            Class<?> caller = Reflection.getCallerClass();
            return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
        }
    

    对于loadClass而言,它最后执行的方法如下,它的初始化默认是为false的。

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

    由于它的方法是protected修饰的,所以子类无法继承。但是我们有反射呀...在Java8中,有了反射就可以随时随地的扒下JVM的内裤了。。。

    相关文章

      网友评论

          本文标题:类加载的过程

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