JVM类的加载、连接、初始化、双亲委派

Java虛拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制。

Class文件由类加载器加载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能,这里就是我们经常能见到的Class类。

一般来说类加载可以分为加载、链接、初始化三个阶段,如下图:
在这里插入图片描述

类的加载

类的加载指的是将类的 . class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。在这个阶段,JVM主要完成三件事:

  1. 通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件)。而获取的方式,可以通过jar包、war包、网络中获取、JSP文件生成等方式。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。这里只是转化了数据结构,并未合并数据。(方法区就是用来存放已被加载的类信息,常量,静态变量,编译后的代码的运行时内存区域)。
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。这个Class对象并没有规定是在Java堆内存中,它比较特殊,虽为对象,但存放在方法区中。

类的连接

类的加载过程后生成了类的java.lang.Class对象,接着会进入连接阶段,连接阶段负责将类的二进制数据合并入JRE (Java运行时环境)中。类的连接大致分三个阶段。

  1. 验证:验证被加载后的类是否有正确的结构,类数据是否会符合虚拟机的要求,确保不会危害虚拟机安全。

  2. 准备:为类的静态变量(static filed)在方法区分配内存,并赋默认初值(如int =0,boolean=false;引用类型的默认值=null)。如static int a = 100;

    • 静态变量a就会在准备阶段被赋默认值0。
    • 对于一般的成员变量是在类实例化时候,随对象一起分配在堆内存中。
    • 另外,静态常量(static final filed) 会在准备阶段赋程序设定的初值,如static final int a = 666;静态常量a就会在准备阶段被直接赋值为666,对于静态变量,这个操作是在初始化阶段进行的。
  3. 解析:将类的二进制数据中的符号引用换为直接引用。

类的初始化

类初始化是类加载的最后一步,除了加载阶段,用户可以通过自定义的类加载器参与,其他阶段都完全由虚拟机主导和控制。到了初始化阶段才真正执行Java代码。类的初始化的主要工作是为静态变量赋程序设定的初值。如static int a = 100;在准备阶段,a被赋默认值0,在初始化阶段就会被赋值为100。
Java虚拟机规范中严格规定了有且只有4种情况必须对类进行初始化:

  1. 使用new字节码指令创建类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候,对应类必须进行过初始化。
  2. 通过java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则要首先进行初始化。
  3. 当对子类进行初始化时,如果他的父类还没有进行初始化,那么先对父类进行初始化。
  4. 对于静态字段,只有直接定义这个字段的类才会被初始化,因此,通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化,当虚拟机启动时,用户需要指定一个主类(包含main()方法的类),虚拟机会首先初始化这个类。

双亲委派机制

讲到类加载必须提的就是双亲委派模型,在JVM中并不是一次性把所有的文件都加载到,而是一步一步的按照需要来加载。比如JVM启动时,会通过不同的类加载器加载不同的类。当用户在自己的代码中,需要某些额外的类时,再通过加载机制加载到JVM中,并且存放一段时间,便于频繁使用。对 于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。

双亲委派的整体流程图如下
在这里插入图片描述
虚拟机提供了3种类加载器3种类加载器,引导(Bootstrap)类加载器、扩展
(Extension)类加载器、系统(System) 类加载器(也称应用类加载器) ;以及父类加载器为AppClassLoader的自定义类加载器。

  1. 启动Bootstrap 类加载器(Bootstrap class loader)( 由C实现,没有父类)
    启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_ HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt. jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
  2. 扩展类加载器(Extensions class loader)(由Jaava语言实现,父类加载器为null)
    扩展类加载器是指Sun公司(已被0racle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的, 是Launcher的静态内部类,它负责加载 <JAVA_ HOME>/lib/ext目录下或者由系统变量-Djava. ext. dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器(大家可以将自己写的工具包放 到这个目录下,可以方便自己使用)。
  3. 系统类加载器(AppClassLoader / SystemAppClass )( 由Java语言实现,父类加载器为ExtClassLoader )
    也称应用程序加载器,它负责加载系统类路径java -classpath或-D java.class.path指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器, 通过ClassLoader#getSystemClassLoader ()方法可以获取到该类加载器。

这3种类加载器之间存在着父子关系(区别于java里的继承),子加载器保存着父加载器的引用。当一个类加载器需要加载一个目标类时,会先委托父加载器去加载,然后父加载器会在自己的加载路径中搜索目标类,父加载器在自己的加载范围中找不到时,才会交还给子加载器加载目标类。

采用双亲委托模式可以避免类加载混乱,而且还将类分层次了,例如 java 中lang包下的类在jvm启动时就被启动类加载器加载了,而用户一些代码类则由应用程序类加载器(AppClassLoader)加载,基于双亲委托模式,就算用户定义了与lang包中一样的类,最终还是由应用程序类加载器委托给启动类加载器去加载,这个时候启动类加载器发现已经加载过了lang包下的类了,所以两者都不会再重新加载。当然,如果使用者通过自定义的类加载器可以强行打破这种双亲委托模型,但也会成功的,java安全管理器抛出将会抛出java.lang.SecurityException异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值