我们所编写的java程序是.java文件,这个文件经过javac编译之后就成为了.class文件*,运行java进程的时候JVM就需要读取.class中的内容,并执行里面的指令。
这里的读取.class指令就是类加载过程,把类设计的字节码从硬盘中读取到内存中,把这个.class中的指令编程类对象。
JVM的五个环节:1)加载 把.class文件找到,代码中先见到类的名字然后进一步找打对应的.class文件(涉及到一系列的目录查找)之后打开并读取文件内容。
2)验证读到的.class文件的数据是否正确,是否合法。(java标准文档中明确定义了.class文件的格式是怎样的)。
3)准备:分配内存空间,最终得到的类对象需要内存存储,根据已经读到的内容,确定出类对象所需要的内存空间,并申请这样的内存空间,并且把内存空间中的所有内容都初始化为0,。
这里初始化为0的主要目的就是我们原本不知道这个内存空间是被其他的变量等用过,里面是否保存的仍然是之前使用过的值,这个值可能会对当前程序产生一些影响,将其全部初始化为0这样可以避免一些BUG。
4)解析,主要针对字符串常量进行处理。
解析阶段是java虚拟机常量池内的额符号引用替换为直接引用的过程,也是初始化常量的过程。
由于我们所进行的累的加载过程是针对.class文件进行的,但是文件当中又没有地址这样脏概念,地址是内存的地址,因此在这里我们使用偏移量带代替"地址"这样的概念。
我们可以通过偏移量来描述字符串常量在文件中所处的位置,我们可以理解为有一个字符串"hello" 在硬盘当中有一个位置,这个字符串常量距离这个位置有多远,这个距离就可以理解偏移量,当将.class文件从硬盘中读取到内存中后,就会根据这个偏移量确定字符串常量在内存中的位置,此时字符串常量的引用就从符号引用变为直接引用。
5)初始化:针对类对象的最终初始化。
这里的初始化确定的类对象中的成员最终的值,在准备过程中的初始化是为了防止由于内存空间的值不确定的情况下可能产生BUG因此才将内存空间的值才全赋值为0,而这里确定的是类对象中静态成员的最终的值,执行的是静态代码块。
在这个阶段如果这个类对象还有父类并且这个父类还没有触发类加载这样的过程那么,在这里机会触发类加载过程。
双亲委派模型:双亲委派模型是类加载1过程中的一部分,目的就是找出.class文件在硬盘中的位置。
JVM已经内置了一些类加载器来完成上述过程(JVM的功能模块)。
JVM默认有三个类加载器:
BootstrapClassLoader 负责加载标准库中的类 (标准库有专门存放的位置)。
ExtensiveClassLoader 负责加载拓展类 (JVM厂商,会希望针对java的功能进行一些拓展)
ApplicationClassLoader 负责加载第三方库中的列/自己写的类型中的类 。
我们可以使用亲人的方式便是他们之间的关系: (爷爷)BootstrapClassLoader-> (父亲) ExtensiveClassLoader ->(儿子)ApplicationClassLoader。
这里的意思并不是extend(继承)的关系而是夹在其中有一个parent这样的引用来指向父亲。
在JVM加载器中 输入:类的全限定名。 输出:.class文件的位置。
当全限定名输入的时候会先经过ApplicationClassLoader但是并不会直接在里面寻找而是继续向上传输经过PlatformClassloader和 BootstrapClassLoader 之后上面就没有了才会在BootstrapClassLoader 中进行查找如果找到了就执行类加载过程中的其他过程如果没有找到,则会依次向下进行逐个查找如果都没有找到就会抛出ClassNotFoundException。
至于双亲委派模型为什么要这么做,那是因为程序员也不可能完全知道java标准库中有哪些类,类的名称是啥,因此很有可能程序员自己创建的类和标准库中的列有重名的情况,因此在程序中如果程序员自己定义的类替换了代码中的标准库中的类那么就会产生严重错误,造成严重的后果。
在这个过程中java标准库的优先级最高,扩展库次之,第三方库最低。