Java类加载的过程

类加载过程

一、类加载过程:

1.加载:将class文件导入内存

加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在内存生成一个代表这个类的java.lang.Class对象(hotspot虚拟机Class对象存储在方法区),作为对方法区中这些数据的访问入口。

加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
注意:

1. java类的加载、连接、初始化、都是运行时动态完成的。和那些编译时需要进行连接的语言不同。
2. Java程序可以动态扩展是由运行期动态加载和动态链接实现的;比如:如果编写一个使用接口的应用程序,可以等到运行时再指定其实际的实现(多态),解析过程有时候还可以在初始化之后执行;比如:动态绑定(多态);
2.连接
2.1验证:检查载入Class文件数据的正确性

验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:

1. 文件格式的验证:验证加载到内存class文件是否符合规范。
2. 元数据的验证:对字节码描述信息进行语义分析。(是否继承自object、是否继承自final标记类、是否实现了接口中的方法等)
3. 字节码验证:主要是进行数据流和控制流的分析,保证被校验类的方法在运行时不会危害虚拟机。(数据类型转换有效性、指令跳转是否合法(跳转到方法体外))
4. 符号引用验证:对常量池中各种符号信息进行匹配校验(是否存在、是否有访问权限等)。确保后面的解析可以正常进行。
2.2准备:给类的静态变量分配存储空间;

准备阶段是正式为类静态变量分配内存并设置类变量默认值的阶段,这些内存都将在方法区中分配

1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
        举例 
        public static int a=123;
        准备阶段为a在方法区分配空间、默认值为0.  后面初始化阶段会将a赋值为123.
        public final static int a=123;
        编译阶段就会为a生成constant属性。准备阶段会根据constant为其赋值。

我们可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中。

2.3.解析:将符号引用转成直接引用;
解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程 
3.初始化:对类的静态变量,静态代码块执行初始化操作

初始化时机:(在这些时机如果类未初始化,则进行初始化操作)

3.1类初始化时机:
1. 实例化对象时(new)
2. 读取、设置类静态变量时(getstatic、putstatic)注意* 被final修饰的、已经在编译期把结果放入常量池的静态字段除外
3. 调用静态方法时(invokestatic)
4. 反射调用类时:Class.forName(“java.lang.Object”),注意* Object.class不会触发初始化
5. 初始化子类时,如果父类未初始化,先初始化父类。
6. 虚拟机启动时,会先初始化main方法所在的类。

只有上述这几种主动引用会触发类初始化。此外被动引用都不会触发。

举例:
1.通过子类引用父类静态字段不会导致子类初始化。

在这里插入图片描述
2.通过数组定义来引用类,不会触发类的初始化。

在这里插入图片描述
3.获取类的常量(字面量)不会触发类的初始化。编译阶段通过常量池优化将对常量a的引用转化为对Test常量池的引用。

在这里插入图片描述
4.获取累的常量(域常量)会触发类初始化!!!!!!b在连接的准备阶段为0

在这里插入图片描述

5.Object.class 不会触发类初始化,Class.forName(“java.lang.Object”)会触发

在这里插入图片描述

3.2接口初始化时机:
 1.接口中不能使用static{}代码块,但在编译时会为接口生成“<clinit>”类构造器,用于初始化接口中定义的成员变量。

 2.接口初始化不会初始化父接口(父接口未初始化),只有真正使用到父接口时(引用父接口中定义的常量)才会初始化父接口。
3.3初始化顺序:

1.不考虑父类情况:

一个类中,<clinit>()方法顺序是静态变量、静态代码块在类中出现的顺序。

静态代码块只能访问定义在他之前的静态变量、之后的静态变量只能赋值。

在这里插入图片描述

2.父类的<clinit>()方法先与子类执行。也就是说父类的静态代码块先于子类静态代码块。

注意*
1.同一个类加载器下,一个类只会初始化一次,()方法只会被执行一次。
2. <clinit> ()方法线程安全,多线程访问一个线程执行<clinit>()方法,其他线程阻塞。()执行完后其他线程不会再执行。

4 <init>:对象实例初始化的方法
4.1 执行时机:对象实例化时

实例化有4种方法:

1. new
2. XXX.class.newInstance()或XXX.class.getDeclaredConstructor().newInstance()
3. 调用对象的clone方法
4. 通过 java.io.ObjectInputStream 类的 getObject() 方法反序列化
4.2.clinit和init区别:

1.执行时机不同:init方法在new 对象时候执行。clinit在类加载过程的初始化阶段执行。
2.作用不同:init方法是对象构造器,对非静态变量、代码块进行初始化,clinit是class类构造器对静态变量,静态代码块进行初始化

没有父类的情况:

1)类的静态属性
2)类的静态代码块
3)类的非静态属性
4)类的非静态代码块
5)构造方法
有父类的情况:

1)父类的静态属性
2)父类的静态代码块
3)子类的静态属性
4)子类的静态代码块
5)父类的非静态属性
6)父类的非静态代码块
7)父类构造方法
8)子类非静态属性
9)子类非静态代码块
10)子类构造方法

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值