浅析类的初始化过程

note:

jdk的是java的开发工具,jre是运行java编译的class文件的工具,其中jre中包含jvm和一些运行java项目时必须的工具包

在项目运行时,java文件先转换成虚拟机能识别的指令序列(编译成java字节码文件),加载到虚拟机中,加载后的java的文件存放于jvm的方法区,实际执行运到某段代码时,虚拟机会去方法区内找到对应的代码并执行

Java 虚拟机中的类加载过程,从calss文件到内存中的类,按先后顺序需要加载,链接和初始化三步骤

加载:

在Java中数据类型主要分为基本数据类型和引用数据类型,基本数据类型在java虚拟机中是预先定义好的,引用数据类型主要包括类,接口,数组和泛型,其中泛型在编译过程中会被擦除,实际上只剩三种数据类型,数组,类,接口,

数组是在java虚拟机中直接生成的,其他的两种都对应的字节流文件,需要通过加载的方式进入java虚拟机;虚拟机规范并没有指明二进制字节流要从一个Class文件获取,或者说根本没有指明从哪里获取、怎样获取。这些class文件的主要来源java程序编译的calss文件

,以及从网络获取的,最典型的应用就是Applet,以及运行时动态生成的,最典型的是动态代理技术等多种方式。

加载:是查找字节流,并且据此创建类的过程;从Java虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。

除了启动类的加载器,其他类的加载器都是java.lang.ClassLoader 的子类。都有对应的java对象,这些类加载器都需要别的加载器,例如启动类加载器,先加载到java虚拟机的内存中,方能执行类加载 

从开发者的角度,类加载器可以细分为:

启动(Bootstrap)类加载器:负责将 Java_Home/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

应用程序(Application)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器。

除此之外,还有自定义的类加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合关系来实现,而不是通过继承;

双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

使用双亲委派模式的好处:

使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行.。在java虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的,即便是同=一窜字节流,由不同的类加载器加载是,也会是两个两个不同的类

链接: 

链接是指把创建的类合并到java虚拟机中,使之能够执行,这个过程给为验证,准备,解析三个阶段。

验证的目的是确保被加载类能满足Java将虚拟机的约束条件(说白了就是格式,内容满不满足要求)。

准备阶段,一是为了被加载类的静态字段分配内存,java类中的静态字段的初始化会在稍后的初始化阶段进行;二是为了给被加载的java类的创建一个符号引用,这个

符号引用在内存就代表着指定的类,这个符号引用一般都能够无歧义的定义到对应的目标上

解析阶段:解析阶段正式把这些符号引用转化为真实引用,如果符号引用指向一个未被加载的类或者未被加载的方法,那么会触发这个类的加载(但是不一定会触发这个类的链接和初始化)

初始化:

在java中初始化一个静态字段,可以在声明时直接赋值,也可以在静态代码块中进行赋值;如果静态字段放在成员变量的位置且用final进行修饰的,并且它的类型是基本类型或者是字符串,

那么在初始化的时候,那么该字段将被编译器标记为常量值(ConstantValue),其初始化由java虚拟机完成;除此之外的赋值化和静态代码块中赋值的操作,都会被java编译器置于同一 方法中 

并且命名为<clinit>;类加载的最后一步是初始化,便是为了常量的字段赋值,以及执行<clinit>的过程;java虚拟机通过加锁来确定这个过程只执行一次

常见的触发类初始化的时间:

1.当虚拟机启动时,初始化用户指定的主类

2.当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类

3.当遇到调用静态方法的指令时,初始化该静态方法所在的类;

4.当遇到访问静态字段的指令时,初始化该静态字段所在的类;

5.子类的初始化会触发父类的初始化;

6.如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;

7.使用反射 API 对某个类进行反射调用时,初始化这个类;

8.当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。

 

 

 

 


 

 

 

 

  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值