Java的类装载体系

在Java的沙箱中,类装载器体系结构的第一道防线。因为java的字节码是由类装载器装载入Java虚拟机的。
不同的类装载器提供不同的命名空间,命名空间可以隔离不同的类装载喊之间装载的类,有助于安全的实现。

一、class文件检验器

class文件检验器保证装载的class文件内容有正确的结构,而且这些class文件相互协调一致。
class文件检验器只是在字节码执行之前对class文件进行一次分析检验它的完整性,遇到跳转指令都进行检查,确认跳转的指令会跳到另一个合法的指令,
否则,非法的指令,导致虚拟机的崩溃。

class文件检验器要进行4趟独立的扫描来完成它的操作。

1.第一趟:class文件的结构检查
比如每个class文件都必须固定以4个字节:
0xcafe babe
这就是java的class文件的魔数,每个class文件都要以它为开始。

2.第二趟:类型数据的语义检查

3.第三趟:字节码验证

4.第四趟:符号引用的验证

Java的类装载体系


Java虚拟机通过
一、装载
二、连接
1.验证
2.准备
3.(可选的)解析
三、初始化

,经过这些步骤,使得一个Java类型可以被正在运行的程序使用,当然解析是一个例外,可以在初始化后再解析。



装载

装载阶段由三个基本动作组成,要装载一个类型,Java虚拟机必须:
1.通过该类型的完全限定名,产生一个代表该类型的二进制数据流;
2.解析这个二进制数据流为方法区的内部数据结构;
3.创建一个表示该类型的java.lang.Class类的实例。
有了类型的二进制数据后,Java虚拟机必须对这些数据进行足够的处理,然后才创建类java.lang.Class的实例对象,虚拟机必须把这些二进制数据解析为与实现相关的内部数据结构。
装载步骤的最终产品就是Class类的实例对象,它成为Java程序与内部数据结构之间的接口。要访问关于该类型的信息(它们是储存在内部数据结构中的),程序就要调用该类型对应的Class实例对象的方法。
这样一个过程,就是把一个类型的二进制数据解析为方法区内中的内部数据结构,并在堆上建立一个Class对象的过程,这被为“创建”类型。
比如,在装载过程,虚拟机必须解析代表类型的二进制数据流。在解析过程期间,大多数虚拟机会检查二进制数据以确保数据全部是预期的格式。Java class文件格式的解析可能检查魔数,确保每一个部分在位置,有正确的长度,验证文件不是太长或者太短。虽然这些检查在装载期间完成,是在正式的连接阶段之前进行,但它们仍然属于逻辑上的验证。检查被装载的类型是否有任何问题的整个过程都属于验证。
双亲委托加载机制

验证

在正式的验证阶段首先确保各个类之间二进制兼容的检查:
1.检查final的类不能拥有子类;
2.检查final的方法不能被覆盖;
3.确保在类型和父类型之间没有不兼容的声明
  比如,两个方法有相同的名字,参数在数量、顺序和类型都相同,但返回值却不相同。当然检查类型和父类型,父类型必须是被初始化的,所以父类型肯定是已经被装载的。
4.检查所有的常量池入口相互之间一致;
5.检查常量池中的所有的特殊字符串(类名、字段名和方法名、字段描述符和方法描述符)是否符合格式;
6.检查字节码的完整性。
虚拟机必须为它执行的每个方法检验字节码的完整性,比如,不能因为一个超出了末尾的跳转指令就导致虚拟机崩溃。
虚拟机的实现没有强制在验证阶段进行字节码验证,比如,可以自由地选择在执行每条语句时单独进行验证,不过,Java虚拟机指令集设计的一个目标就得使得字节码流可以一次性验证字节流,而不是在程序执行时动态验证,使得Java程序运行速度得到很大的提高。

准备

虚拟机装载了一个类型,并且进行了一系列的选择的验证操作之后,就可以进入准备阶段了。
在准备阶段,虚拟机为类变量分配内存,设置默认值,在准备阶段,是不会执行Java代码的,在准备阶段,虚拟机把变量新分配的内存根据类型设置默认值。

类型                         默认值
======================================================
int                        0
long                       0L
short                      (short)0
char                       '\u0000'
byte                       (byte)0
boolean                    false
float                      0.0f
double                     0.0d
reference                  null
======================================================

在虚拟机内部,boolean往往用int类型实现表示的,会被默认为0(boolean的false)。


解析

连接经过了验证和准备两个阶段,可以进入第三个阶段--解析,解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用的过程。

初始化

为准备让一个类或者接口被首次主动使用,最后一个步骤就是初始化,也就是为类变量赋予正确的初始值,这里的正确初始值,是我们在写程序时,在程序上显式地给类变量赋予的值,这里初始化的值是相对于连接里准备阶段的默认初始值而言的。
在Java代码中,正确的初始化值是通过类变量初始化语句或者静态初始化语句赋值的。
class A{
    //类变量初始化语句
    static int width = 0;
    
    //静态初始化语句
    static {
        width = 2;
    }

}
所有类变量初始化语句或者静态初始化语句都被Java编译器收集在一起,放在一个特殊的方法。对类来说,叫类初始化方法;对接口来说,叫接口初始化方法,在类和接口的class文件中,这个方法被称为"<clinit>"。Java程序方法是无法调用到"<clinit>"方法的,只能由Java虚拟机调用。
初始化一个类包含两个步骤:
1.如果类型存在一个直接父类,而父类还没初始化的时候,则先初始化父类;
2.如果类存在一个初始化方法"<clinit>"方法,则执行初始化方法。
当然,初始化父类也是要通过这两个步骤的,所以第一次初始化的类型永远都是是Object。而初始化接口则不需要先初始化父接口,如果接口有"<clinit>"方法,则虚拟机则只会调用接口本身的初始化方法。
<clinit>方法并不会显示地调用父类的<clinit>方法,如果有多个线程想要初始化一个类,则虚拟机会确保只能一个线程初始化这个类,

虚拟机必须在类或者接口首次主动使用时初始化,下面是主动使用的情形。

主动使用

主动使用的6种情形:
1.当创建某个类的新实例时(或者通过字节码中执行new指令;或者通过不明确地创建、反射、clone或者反序列化);
2.当调用某个类的静态方法时(即在字节码中执行invokestatic指令时);
3.当使用某个类或者接口的静态字段时,或者对该字段赋值时(即在字节码中,执行getstatic或者putstatic指令时),用final修改的静态字段除外,它被初始化成一个编译时的常量表达式;
4.当调用Java API中的某些反射方法时,比如类Class中的方法或者java.lang.reflect包中类的方法;
5.当初始化某个类的子类时(某个类初始化时,要求它的父类必须已被初始化了);
6.当虚拟机实例启动时,启动类即main方法的启动类。

被动使用

使用类或者接口声明的非常量的静态字段都是主动使用。比如,类中声明的字段可能会被子类引用;接口中声明的字段可能会被子接口或者实现了这个接口类引用。对于子类、子接口和实现了接口的类来说,这就是被动使用--使用它们不会触发它们的初始化。只有当字段是类或者接口声明的时候才是主动使用。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值