JVM成神之路——类加载机制

虚拟机类加载机制

概述

虚拟机把class⽂件加载到内存,并对数据进⾏检验、转换解析和初始化等,直到成为被虚拟机直接使⽤的Java类型,这个过程被称为Java的类加载机制。 ⽽且Java的天⽣特性之⼀动态加载和动态调⽤,也是因为这⼀机制,为Java语⾔提供了很强的扩展性

类的加载机制

类加载的⽣命周期被分为以下⼏个阶段:加载、验证、准备、解析、初始化、使⽤和卸载⼏个阶段。其中验证、准备、解析三个阶段被总称为连接。

*加载、验证、准备、初始化、卸载这五步的顺序是固定的,⽽解析阶段却不⼀定,它可以在初始化之后再进⾏,这是因为为了⽀持Java语⾔的动态绑定特性。

*关于在什么情况下开始类加载,Java没有规范,但在什么时候开始初始化Java却有⾮常严格的规范:

  1. 在遇到new、getstatic、putstatic,invoke字节码的时候(读取静态字段、调⽤静态⽅法、使⽤new 关键字)
  2. 虚拟机启动主类的时候,⾸先会初始化主类
  3. 使⽤Java.lang.reflect包进⾏反射调⽤的时候
  4. 当初始化⼀个类的时候,若该⽗类没有被初始化,则先初始化⽗类
  5. 如果⼀个java.lang.invoke.MethodHandle实例 Y后的解 析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的⽅法句柄,则先初始化句柄对应的类
  6. 当接⼝中有被defult修饰的⽅法,则在初始化该类的的时候,先初始化接⼝
  7. 《Java虚拟机规范》定义了有且只有这六种场景发⽣的叫做主动引⽤,其他都为被动引⽤

对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其⼦类来引⽤⽗类中 定义的静态字段,只会触发⽗类的初始化⽽不会触发⼦类的初始化。

接⼝的初始化:由于接⼝中不能使⽤static语句块,但虚拟机任然会为它⽣成<clinit>类构造器,但接⼝不是实现类要先初始化所有的接⼝,⽽是当真正使⽤的时候才会初始化接⼝

类的加载

“加载”阶段是整个过程中的⼀个阶段。在加载阶段, Java虚拟机需要完成以下三件事情: 

1)通过⼀个类的全限定名来获取定义此类的⼆进制字节流。

 2)将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构。

 3)在内存中⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类的各种数据 的访问⼊⼝

*⾮数组类型的加载阶段是开发⼈员可控性Y强的阶段。

*加载过程,既可以有虚拟机系统内置的类加载器,也可以是⽤户⾃⼰定义的类加载器,开发⼈员通过定义⾃⼰的类加载器去控制字节流的获取⽅式(重写⼀个类加载器的findClass()或 loadClass()⽅法。

*对于数组类,它本身不通过类加载器创建,它是由Java虚拟机直接在内存中动态构造出来的。但数组类的元素类型,Y终还是要通过类加载器,数组类的创建有如下规则:

 如果数组的组件类型是引⽤类型,加载这个组件类型,数组C将被标识在加载该组件类型的类加载器的类名称空间上。

 如果数组的组件类型不是引⽤类型,Java虚拟机将会把数 组C标记为与引导类加载器关联。 

数组类的可访问性与它的组件类型的可访问性⼀致,如果组件类型不是引⽤类型,它的数组类的可访问性将默认为public,可被所有的类和接⼝访问到。

验证

类的验证就是验证class的包含的信息是否符合Java虚拟机的规范,也就是说能不能被Java虚拟机所认可

⽂件格式验证:验证⼆进制字节流是否符合class⽂件规范(魔数、版本号、常量池、访问标志)

第⼀阶段是基于⼆进制字节流的验证,第⼀阶段完成后,⼆进制字节流才会流⼊,后⾯三个阶段是对

⽅法体存储结构进⾏验证

元数据验证:对字节码描述的信息进⾏语义分析,看是否符合规范(是否有⽗类,是否继承了不允许的类,字段⽅法等等)

字节码验证: 数据流分析和控制流分析,确定程序语义是合法的(对⽅法体的验证,如错把int当作long 类型存⼊)符号引⽤验证:这⼀阶段将在虚拟机把符号引⽤转化为直接引⽤期间,这期间就是解析阶段。符号引⽤验证可以看作是对除类⾃身以外(常量池中的符号引⽤)的信息进⾏匹配性验证。 符号引⽤验证的主要⽬的是确保解析⾏为能正常执⾏。

准备

准备阶段:为类中定义的变量(即静态变量)分配内存并设置类变量初始值类变量会随着class对象被分配在Java堆⾥。类变量不包括实例变量,⽽初始值为各个数据类型的零值。

 !!!特殊情况:如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量值就会被初始化为 ConstantValue属性所指定的初始值(即设定值)

解析

 解析: 解析阶段是虚拟机将常量池内的符号引⽤替换为直接引⽤的过程。 ·

符号引⽤: 符号引⽤以⼀组符号来描述所引⽤的⽬标,符号可以是任何形式的字⾯量。符号引⽤与虚拟机实现的 内存布局⽆关。

直接引⽤: 指针、相对偏移量或句柄。直接引⽤是和虚拟机实现的内存布局直接相关的。

 解析动作主要针对类或接⼝、字段、类⽅法、接⼝⽅法、⽅法类型、⽅法句柄和调⽤点限 定符这7 类符号引⽤进⾏。

初始化

初始化阶段:Java虚拟机才真正开始执⾏类中编写的Java程序代码,将主导权移交给应⽤程序。

更直观的说,初始化阶段就是执⾏类构造器<clinit>⽅法的过程,它是Javac编译器的⾃动⽣成物。<clinit>⽅法是由编译器⾃动收集类中的所有类变量的赋值动作和静态语句块(static{}块)。

同⼀个类加载器下,⼀个类型只会被初始化⼀次。

类加载器

Java设计团队有意把类加载阶段中的“通过⼀个类的全限定名来获取描述该类的⼆进制字节流”这个动作放到Java虚拟机外部去实现,以便让应⽤程序⾃⼰决定如何去获取所需的类。实现这个动作的代码被称为“类加载器“。

类与类加载器:

对于任意⼀个类,都必须由加载它的类加载器和这个类本身⼀起共同确⽴其在Java虚拟机中的唯⼀性。

⽐较两个类是否“相等”,类加载器必须相等,其次是类相等,两者缺⼀不可

双亲委派模型

站在虚拟机的⻆度,只有两种的类加载器: ⼀种是启动类加载器 (Bootstrap ClassLoader),这个类加载器使⽤C++语⾔实现,是虚拟机⾃身的⼀部分; 

另外⼀种就是其他所有的类加载器,这些类加载器都由Java语⾔实现,独⽴存在于虚拟机外部,并且全都继承⾃抽象类 java.lang.ClassLoader

*站在开发者⻆度看有三层类加载器:

  1. 启动类加载器:这个类加载器负责加载存放在<JAVA_HOME> \lib⽬录,或者被-Xbootclasspath参数所指定的路径中存放的
  2. 扩展类加载器:它负责加载<JAVA_HOME> \lib\ext⽬录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库,这是⼀种Java系统类库的扩展机制,可在程序中使⽤扩展类加载器来加载class⽂件 
  3. 应⽤程序类加载器:它负责加载⽤户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使⽤这个类加载器。

 上图的层次关系被称为类加载器的双亲委派模型。它的⼯作过程是:

如果⼀个类加载器收到了类加载的请求,他⾸先不会⾃⼰去尝试加载这个类,⽽是将这个请求委派给⽗类加载器去完成,所有的加载器Y后都会传送到Y顶层的启动类加载器,只有当⽗类加载器⽆法完成这个请求时(搜索范围中没有找到类),⼦类加载器才会⾃⼰尝试完成加载。使⽤双亲委派模型可以使类随着类加载器⼀起具备了⼀种带有优先级的层次关系

破坏双亲委派模型

  1. ⾯对已经存在⽤户⾃定义类加载器的代码,jdk后期在classLOader添加了⼀个findClass⽅法,如果⽗类加载失败,会调⽤⾃⼰的find CLass⽅法,既不影响⽤户,⼜符合双亲委派模型
  2. 线程上下⽂加载器:这是⼀种⽗类加载器去请求⼦类加载器完成类加载的⾏为。可⽤setContextClassLoader⽅法进⾏设置。Java涉及SPI的加载基本都采⽤这种⽅式,例如JNDI、JDBC、JCE、JAXB、JBI等
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值