Java类加载机制----虚拟机解析

类加载

其全生命周期过程分为:加载(Loading)、连接(Linking)、初始化(Initialization)、使用(Using)、卸载(Unloading);连接,是指将创建成的类合并至 Java 虚拟机中,使之能够执行的过程,具体指:验证、准备、解析;而解析与初始化的顺序并不是一定的,解析可以在初始化之后,这是为了支持运行时绑定(即动态绑定或晚期绑定)。
lyt自制
图1-1 类加载生命周期

加载

加载,是指查找字节流,并且据此创建类的过程。加载时,虚拟机需要知道哪些信息?然后做什么?这些信息来源是否固定?
1.通过一个类的全限定名来获取定义此类的二进制字节流;
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
上述三点并不规范,尤其“通过一个类的全限定名来获取定义此类的二进制字节流”,二进制字节流的来源并非只来源于class文件,如zip中读取、由jsp生成的class类、从网络中获取(Applet)等。此外,数组类的加载直接由Java虚拟机直接创建。

验证

很简单的来理解,就是为了确保class字节流中的信息符合虚拟机的要求,不会对虚拟机造成危害,验证内容包含以下四个内容:
1.文件格式验证;
如是否魔数开头0xCAFEBABE,常量池中的常量是否有不被支持的常量类型,Utf8型的常量是否有不符合uft8的数据等;
2.元数据验证;
是否有父类,是否实现了父类及接口要求的方法等;
3.字节码验证;
检验语法语义是否符合逻辑,主要靠数据流和控制流进行分析。
4.符号引用验证。
是否能找到该全限定名的类,如NoSuchMethodError;private/public是否可被当前类访问等。
有人会问为啥要检验,编译器不是都做好这一步规范了吗?难道JDK编译器还会有问题?
我只会说字节码注入了解下,旁油~

准备

指正式为类变量分配内存并设置类初始值的阶段,即被static修饰的类变量;回忆Java虚拟机内存划分内容,这块操作均在方法区中进行分配;值得注意的是,类实例并不在此处分配内存;而经验准备阶段的变量值并非就是“=”后所赋予的值,而是“零值”,每个数据类型的“零值”不同。如:

public  static int value = 123;

其值在准备后为0而不是123,因为此时未开始执行任何java方法,只有在初始化阶段后才被赋值为123;
除非使用final将该变量的属性设为ConstantValue,ConstantValue的作用是通知虚拟机自动为静态变量赋值。(PS:static为“只有一份”,final为“不可修改”)如下:

public final static int value = 123;

在这里插入图片描述

解析

是虚拟机将常量池的符号引用替换为直接引用的过程,在 class 文件被加载至 Java 虚拟机之前,这个类无法知道其他类及其方法、字段所对应的具体地址,甚至不知道自己方法、字段的地址。因此,每当需要引用这些成员时,Java 编译器会生成一个符号引用。在运行阶段,这个符号引用一般都能够无歧义地定位到具体目标上。在不同的虚拟机中同一个直接引用一般都不会相同,这很好理解,因为这与虚拟机实现的内存布局相关。
实际上在我们学习虚拟机的过程中了解到哪几种常用需要解析即可:
在这里插入图片描述

初始化

初始化是类加载的最后一步,便是为标记为常量值的字段赋值;除被final直接赋值的静态字段,其余常量的赋值及静态代码块,都会被 Java 编译器置于同一方法中,并把它命名为 < clinit >。
以下五种情况会触发类的初始化:

1)遇到new、getstatic、putstatic、invokestatic这4条指令的时候,如无初始化则触发初始化;即new一个对象实列的时候、读取一个类非final静态字段的时候以及调用其静态方法的时候。
2)使用java.lang.reflect中方法生成实例的时候。
3)初始化一个类,首先会初始化其父类(包括含有默认方法的接口)。
4)虚拟机启动时,用户指定的一个执行主类,比如包含main方法的类。
5)1.7JDK中,MethodHandle指向的方法所在的类也会初始化。

类加载器

		在Java虚拟机中,一个类的唯一性是由类的全限定名及类加载器确定的。

不同的类加载器加载同一串字节码得到不是同一个类,这是由于虚拟机把类字节码放入其中去加载的动作,交由给外部代码模块去实现,这个代码模块就叫“类加载器”。它的由来是为了满足Java Applet的需求,而发展至今时,被广大程序猿们发扬光大:如class文件加密、热部署、类层次划分等。
当我们自己写了个java.lang.Object时,为了不与jre包中同名类冲突,只需编写一个类加载器即可。

双亲委派模型

从虚拟机的角度来看只有两种不同类加载器,即启动类加载器(Bootstrap ClassLoader)和继承自java.lang.ClassLoader的类加载器。

从开发角度则细分为四种:
1)启动类加载器(Bootstrap ClassLoader),由C++实现,是虚拟机的一部分;
2)类扩展加载器(Extension ClassLoader),由sun.misc.Launcherr$ExtClassLoader实现,负责加载 <JAVA_HOME>\ lib\ext目录中的,可直接使用;
3)应用程序类加载器(Application ClassLoader),由sun.misc.Launcher:AppClassLoader实现,这个类加载器是默认的类加载器;
4)自定义类加载器,开发者自定义实现的。
在这里插入图片描述
除了顶层的启动类加载器,其余类加载器均有父类加载器,但一般不以“继承”的方式,而是“内聚组合”的方式现实。这种层次关系叫做“双亲委派模型”,其过程是每一个类加载器收到请求,都会委派给父类去加载,每一层都如此,因此所有的请求最终都会到启动类加载器中;只有当父类反馈无法完成(搜索范围内找不到该类),才由下一层子类去加载。

破坏双亲委派模型

因为该模型并不是一个强制性约束模型,而是一个推荐的开发模式。因不同的需求而去“破坏”该模型设计也是常有,举例以下:
1.在JDK1.2未引入该模型前,当时已存在类加载器和java.lang.ClassLoader,而当自定义类加载器出现时,不得不引入该模型;
2.当加载类信息不能被启动类加载器查找,如JNDI、JDBC等外部接口信息,此时父类加载器会请求子类加载器去加载;
3.热部署,我们知道当一个系统规模足够大(当时并无微服务)或者该系统要求生产机不可重启服务时。

总结

不一定每篇都有总结,懂了啥自己去琢磨,意不意外:),接下来会出一篇关于自定义类加载器实现的文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值