Java加载类过程

类加载过程图解:

这里写图片描述

简介:

以上这张图是博主经过阅读周志明的深入理解Java虚拟机一书,并在网上看了若干帖子之后画出来的,我争取用一句话来
描述每个阶段都做了什么,因为每个阶段做的事情实在有一些多,而我们做为开发者也着实想理解的深入一些,所以,我
接下来用一些文案描述,究竟每个步骤发生了什么,
但是,首先澄清一点。基于博主的理解有限,加上主观判定有可能错误。所以欢迎大家指正。一起学习,我会及时的更新本篇纠正错误。

编译阶段

编译阶段严格意义上来说,并不是类加载阶段中的一环,它是由Java Code转换成class字节码的一部分。关于class文件结构的说明,在书中也有提到,class文件是严格紧凑的,在字节码文件中,从第一位开始,到最后一位结束,按照既有的文件结构依次往下捋顺即可。博主没有做过class文件解析的工作,所以也不在多说。但是有一个比较不错的图解blog可以推荐给大家:https://www.jianshu.com/p/5f3278916b38

编译阶段,值得一提的是静态资源的引用,例如:

class A{
        public static final int a = 1;
}

class B{
        public static final int b = A.a
}

案例我没有测试,意思很明确,如果在类B中引用了类A的静态变量,那么编译完成后。在类B的class字节码常量池中会
有b=1这样的编译案例,也就是说。在编译阶段结束后。静态变量的引用,将会与原来的类彻底无关!

加载阶段

通过一个类的权限类名获取定义此类的二进制流,
将这个字节流所代表的静态存储结构,转换成为方法区的运行时数据结构
在堆中生成代表这个类的class对象,开放出去作为数据的访问入口。

这意思已经很明确了。把二进制流描述的class字节文件,转换成相应的内存结构,保存到方法区中。只是转换内存,其他什么都不做。需要注意的是,这个class对象,并不是实例化对象。诸如我们上面例子中的A.a这样访问的code。作为静态资源访问,类名本身就可以理解为一个类对象即可。

验证阶段

文件格式验证:class文件结构验证,比如开头的魔数,版本号,常量池等等
元数据验证:继承或实现,是否实现了abstract方法或者是接口中的方法?
字节码验证:泛型,类型转换是否有效等等
符号引用验证:除了自身常量池之外的引用,比如权限类名是否可以被找到

验证操作有很多种,我只是拿了其中几个比较好理解的部分进行说明。很多时候我们进行启动的时候,就会抛出
class not found 又或者是泛型转换之类的错误,我想就是这里的问题,虽然现在的IDE很强大了。但是不免启动的
时候还是有很多的错误报出来

准备阶段

    public static final int a = 123

那么a变量将会赋值为123,但是如果把final去掉的话,本阶段,这个变量将会赋值一个0的初始值

解析阶段

符号引用转换为直接引用。这里我只用了一句话来说明解析阶段真正做的事情。直接引用,不管是用直接引用,指针亦或者是偏移量,句柄。任何一种引用,最终都会引用到一个内存地址,而内存地址中则保留着这份引用确定的对象.
符号引用和直接引用的概念大家可以看下面这篇文章:https://blog.csdn.net/zhang6622056/article/details/80270808

初始化阶段

使用new,getstatic,putstatic或invokestatic字节码指令的时候
使用反射类对象实例的时候
当初始化一个类,发现其父类还没有初始化,则首先初始化其父类
main函数的类,必然首先被初始化
当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getstatic,REF_putstatic,REF_invokestatic方法句柄,并且这个方法句柄所对象的类,没有进行过初始化,则首先触发其初始化.

在初始化阶段这里我描述的是执行静态代码块,其实不然。这里有初始化阶段有可能发生的5种情况,必须立即
对类进行初始化
对于最后一条,理解上还有一些困难。容我知识面广了以后再进行完善…

被动引用不会触发立即初始化的2种情况,另外一种在编译阶段已经举例说明.

public class SuperClass {
    static{
        System.out.println("Super class Init");
    }
    public static int value = 3;
}

public class SubClass extends SuperClass{
    static{
        System.out.println("SubClass Init");
    }
}
public class NotInitialialization {


/****
     * 被动引用一:引用父类初始化
     * 通过子类引用父类,只会触发父类的初始化阶段,而不会触发子类的初始化阶段
     * 输出结果为
     * Super class Init
     * 3
      */
    public static void main(String[] args) {
         System.out.println(SubClass.value);
    }


    /****
     * 被动引用二:数组初始化
     * 被动引用案例  数组案例
     * 并不会触发SuperClass的初始化阶段
     * 实际为 new Array,和类本身无关
     * @param args
     */
    public static void main(String[] args) {
       SuperClass[] sca = new SuperClass[10];
   }
}

类加载总结:

以上基本上就是博主理解的所有加载类流程方面的知识点,很窄小,但是基本上可以概括性的先理解一下这几个流程中
主要做的事情。抛出来几个问题,留作思考:

既然验证阶段可能会返回问题,那么加载到方法区运行时结构如果出现问题呢?这个加载流程是不是交叉执行的?
解析阶段在初始化阶段之前,意思把符号引用转换成为直接引用,但是直接引用指向的内存地址没有完成初始化的话,会指向哪里?这个加载流程是不是交叉执行?

加载器的双亲委派模型

这里写图片描述

图中指定了每个加载器负责加载的目录.所谓双亲委派,也就是先让父类先加载,可以理解Bootstrap是Extension的爸爸,Extension对于Application亦然。而我们自己编写的类加载器,这三位加载器的后代了。说白了,加载一个类,任务给到你,你自己不加载,先给爸爸,爸爸给爷爷,爷爷给太爷爷。一个概念。但是不一样的是,这几个加载器每个只加载自己固定的范畴,不属于自己任务的,他们不会加载。如果三个加载器走完,都没有检索到要加载的类,那么就会向下调用到自己这里来,自己才会去尝试加载。这样的加载机制避免了重复加载

破坏双亲委派

类似与JDBC,Java定义标准,由厂商负责实现,如果采用双亲委派,那么BootStrap检查到Driver类的实现类的时候,它无法判定究竟是哪个类,包括mysql,oracle,sql server 每一个都有自己的实现。由此为了保证类加载器能够加载实现类,双亲委派模型被破坏了。
热部署模块,使得模块可以拆卸,热插拔。OSGi被提了出来。

OSGi有待研究!!!!

猜你喜欢

转载自blog.csdn.net/zhang6622056/article/details/80340029