java类的加载过程

文章首发于我的个人博客,到个人博客体验更佳阅读哦

https://www.itqiankun.com/article/1564899910

什么是类的加载过程

一个Java文件从编码完成到最终执行,一般主要包括两个过程:编译和运行,其中编译就是把我们写好的java文件,通过javac命令编译成字节码,也就是我们常说的.class文件,然后运行则是把编译声称的.class文件交给Java虚拟机(JVM)执行。而我们所说的类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
举个通俗点的例子来说,JVM在执行某段代码时,遇到了class A, 然而此时内存中并没有class A的相关信息,于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。由此可见,JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。

类加载的过程讲解

类加载的过程主要分为三个部分:加载、链接、初始化,这三个阶段

第一个部分:加载阶段

简单来说,加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。这里的字节码文件的来源,一般包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译,按照类加载器:一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。

加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情,就是下面的三件
1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

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

在类的加载过程中,相对于类加载过程里面的的连接和初始化阶段而言,加载阶段是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

我记得阿里的pandora就是通过不同的类加载器来加载不同的类,可以去类加载器里面去看阿里的pandora框架知识点

第二个部分:连接阶段

在连接里面又可以被分成3个小阶段,分别是:验证,准备,解析

连接阶段之验证小阶段
验证的目的
主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

1.包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?

2.对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?

3.对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。

4.对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(privatepublic等)
是否可被当前类访问?

验证做的具体内容

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:

1.文件格式验证:验证字节流是否符合Class文件格式的规范;
例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。

2.元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),
以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。

3.字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的,如循环、分支等

4.符号引用验证:确保解析动作能正确执行,比如不能访问引用类的私有方法、全限定名称是否能找到相关的类。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

连接阶段之准备小阶段
在准备阶段是为类的静态变量分配内存,并将其初始化为默认值

主要准备阶段是为类的静态变量分配内存,不会对类的非静态变量分配内存

在准备阶段,为静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。jvm默认的初值是这样的
1.基本类型(intlongshortcharbytebooleanfloatdouble)的默认值为0
2.引用类型的默认值为null
3.finalstatic共同修改的变量,我们通常称为常量,然后常量的默认值为我们程序中设定的值,
比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100

为什么被final和static变量修饰的成员变量在准备阶段的赋值会比较特别呢,这是因为,被final和static修改的变量,我们叫做ConstantValue属性,ConstantValue属性就是这样特殊的属性,至于什么是ConstantValue属性,看这篇文章:https://blog.csdn.net/weixin_43689480/article/details/96099841

连接阶段之解析小阶段
这一阶段的任务就是把常量池中的符号引用转换为直接引用

什么是符号引用,什么是直接引用

符号引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。

直接引用:可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。

第三个部分:初始阶段

类加载过程中的初始化阶段主要做的事就是:

JVM负责主要对类变量(类变量就是static修改的变量)进行初始化

JVM负责主要对类变量(类变量就是static修改的变量)进行初始化主要有两个方式
1.声明静态类变量时指定初始值
2.使用静态代码块为类变量指定初始值
然后这里要注意一下,类变量进行显示赋值和使用静态代码块对类变量进行赋值,这里谁优先呢

这个是看顺序的,比如下面的代码,为什么会这样,暂时不知道,记住就好了

package com.one.jicheng;

public class Zi {
    static int x = 10;
    static{ x = 20; }

    static{ y = 20; }
    static int y = 10;

    public static void main(String[] args) {
        System.out.println("x的值"+Zi.x);
        System.out.println("y的值"+Zi.y);
    }
}

在这里插入图片描述

类的加载过程中里面的初始化时机

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

– 创建类的实例,也就是new的方式

– 访问某个类或接口的静态变量,或者对该静态变量赋值

– 调用类的静态方法

– 反射(如Class.forName(“com.shengsiyuan.Test”))

– 初始化某个类的子类,则其父类也会被初始化

– Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

能看到这里的同学,就帮忙右上角点个赞吧,Thanks♪(・ω・)ノ

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值