白话类加载

2019-11-21  本文已影响0人  瑞瑞余之

无论你看哪个版本JVM书籍,类加载是绕不开的开篇第一课,然而我们对其理解往往受限于JVM繁复的概念,而无法真正消化,本文力求图文结合,用大白话让读者真正理解JVM类加载阶段。
JVM类加载的阶段可以分为:加载、连接、初始化。标准的类加载阶段其实包括了5个步骤,其中连接又分为验证、准备、解析。


类加载阶段

我们一步步来理解:当我们的Java源文件(.java)文件被编译器编译成.class的字节码文件后,这个字节码文件还是存在于我们的硬盘上,我们可以在项目结构中看到这些编译后的class文件:


字节码文件

加载

java虚拟机要操作它,第一步当然是将硬盘上的字节码文件放到内存当中,这个过程就是类加载过程的第一步加载(在刚学习JVM的时候,往往把类加载过程和这个加载阶段混淆,客观来说,我也觉得这两个加载取名不够好)。这里有几点需要明确!

说到这里我们了解了JVM会将编译好的字节码文件加载到虚拟机允许时环境的方法区当中,这些信息会从字节码文件格式转换成方法区运行时数据格式,最重要的是JVM会根据这个类的信息在堆内存当中创建一个类对象

到此为止加载的结果已经清楚了。但还有两个问题是需要解决的:1.类加载的时机,2.类加载的过程。我们先来看第一个问题:

1. 类加载的时机:

Java虚拟机规范对类加载的时机没有明确规定,但肯定是发生在运行时当中。看一些文章会将类的加载时机和类的初始化时机混淆,这里强调一下,类的加载最终目的是将class文件加载到方法区并在堆中创建对应的class对象。而类的初始化是给静态变量赋上正确的值,它发生在加载、连接之后,所以不是一回事。类的加载与类的初始化不同,它不需要等到该类被首次主动使用(后面会解释)时才去加载,JVM运行类加载器在预料到某个类要被使用时就提前加载它。这个时候如果加载出现了错误,JVM不会立即报错,而是在程序首次主动使用它的时候才报告错误,如果这个类一直没有被主动使用,则一直不会报错。

2. 类的加载过程:

首先,我们都知道类的加载是由类加载器完成的,JVM中加载器分为三类:Bootstrap、Extension、App,其中BootStap类加载器是基于JVM的也就是说不同类型的JVM不一样,可以理解成native;Extension和Application类加载器继承自ClassLoader类是Java代码编写的。每类ClassLoader负责加载不同位置的class

A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.Every Class object contains a reference to the ClassLoader that defined it.

译:类加载器是用来加载类文件的一个对象。ClassLoader这个class本身是一个抽象类,ClassLoader可以根据提供的类名定位到对应的类文件,最典型的方式就是将类名转换成文件名。每一个Class对象都可以访问到生成它的类加载器。

这里的Class对象不就是我们说的加载的最终成果么!!!那么ClassLoader如何工作呢:

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.

译:类加载器采用委派模型(即:双亲委派模型)来搜寻类或者资源。每一个类加载器实例都有一个与之关联的父亲加载器。当JVM要求子加载器去找某类或者资源时,子加载器会先委派给父亲加载器,直到顶层的根加载器(BootStrap加载器)
接上面的说,所有的加载任务都会先一级级的传到BootStrap,如果它没找到(类加载器会到自己负责的文件目录中寻找)它会告诉子加载器,子加载器才会开始尝试寻找。它的整个流程如下:


双亲委派机制

在ClassLoader.java中有三个重要方法,对于继承它的类加载器需要去实现:

Class findClass(String name)
Class<?> loadClass
final Class<?> defineClass(String name, byte[] b, int off, int len)

loadClass就是双亲委派的实际过程,我们不妨看看源码:

   protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先判断这个name的class是否加载过,如果加载过c!=null,直接返回
            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
                }
                //此时c==null,表示父加载器也没有找到Class,这时候就自己找啰
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //父加载器没找到Class,只能自己找,调用自己的findClass()
                    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;
        }
    }

可以看到双亲委派的流程是loadClass控制的,真实的去磁盘上找文件是由findClass()方法执行,但是如果看到findClass定义,你可以发现其中没有内容,这就是各个级别的类加载器需要自己复写的方法。我们在findClass中搜索文件你,如果找到文件,则通过defineClass将文件转化成输入流,进而读到内存方法区中,返回一个Class对象。

以上就是JVM对类加载流程的第一步:加载的完整过程及原理,通过类加载器,磁盘上的class文件就存到了内存方法区当中,下一步就是进行连接和初始化啦!

上一篇下一篇

猜你喜欢

热点阅读