Java类加载过程

今天看看java基础,聊聊java的类加载,不知道小伙伴们好不好奇,当我们启动程序的时候,我们所编写的代码计算器怎么执行的呢,计算机时如何知道代码需要做些什么事情呢?

我记得刚刚接触java这门语言的时候,我的第一个程序就是输出"Hello world",哈哈哈,相信有很多小伙伴们有这样的经历,当时没有类似于idea编译器的时候,我们都是先用javac来编译生成.class文件,然后再用java运行所写方法的文件名,这里的文件名和类名要一致,这样程序就可以正确的输出Hello World了,但是在生成class文件到输出Hello world过程中。到底发生了什么事情呢,今天我们就来看看,go

我们先来看一张很熟悉的图:

RH69cn.png

其中在.class在jvm中会发生下面的步骤操作:

RH6GND.png

接下来我们来看看上述没有步骤都发生了什么事情,其实相关的解析网上已经有了很多,大同小异,这里我把自己理解的记录下来,和大家分享一下,有错误之处,欢迎大家来指正,哈哈

加载:

首先就是类加载过程,通过.class文件加载到内存中,这里的.class文件可以来源于本地的.class文件,jar中的.class文件或者网络中的.class文件,这里的加载方式是通过类加载器进行加载的,关于类加载器后面我会详细讲解,这可是个好东西。

加载基本就是干这点事了

连接:

后面就是连接了,连接过程又分为小散步,本别为验证,准备,解析

  • 验证

验证就是用来保证加载进来的字节流符合虚拟机的规范,不会造成安全错误。

验证的主要工作就是元数据的验证,字节码的语义语法是否正确

  • 准备

准备阶段就是为类变量分配内存,并且 赋予初始值,注意这里是类变量的初始化,而且这里的初始值不是实际代码赋予值,而是java数据类型的初始化值,比如int的为0,boolean为false等等

  • 解析:

这里的解析是符号引用转换成直接引用的过程,符号引用和虚拟机是没有关系的,定义在.class文件中,因为在.class还没有加载到虚拟机内存时,对象是没有实际的地址的,所以对象之间的引用只能通过名字来相互进行标识,后面当引用的对象加载到虚拟机内存了,就会把符号引用替换为直接引用,这里的直接引用可以理解为对象加载到虚拟机内存中的地址。

注:解析过程是可以发生在初始化过程之前或者之后,这里就涉及到了java的动态绑定和静态绑定,

java动态绑定和静态绑定

这篇文章把静态绑定和动态绑定写的很好,小伙伴们可以看一下,动态绑定其实目的是为了多态的机制,详细大家在项目中多态应该用到很多的,这也是java的一大特色之一。

初始化:

最后就是初始化了,这里的初始化时类的初始化,记住是类,初始化也是对类变量的初始化过程,给类变量赋予实际的值,那么小伙伴就会问了,我的成员变量是在什么时候进行初始化呢? 成员变量的初始化是在类实例化的时候,这里是不是很模糊,我也是,然后我看到了一个很逆天的例子,分享给大家,如下:

public class StaticTest
{
    public static void main(String[] args)
    {
        staticFunction();
    }
    static StaticTest st = new StaticTest();
    static
    {
        c=3;
        System.out.println("1");
    }
    {
        System.out.println("2");
        System.out.println(c);
    }
    StaticTest()
    {
        System.out.println("3");
        System.out.println("a="+a+",b="+b);
    }
    public static void staticFunction(){
        System.out.println("4");
    }
    int a=110;
    static int b =112;
    static int c;
}

你们的第一感觉是会输出什么,我的第一感觉是,在main入口之前就会有初始化阶段,然后就会运行 static StaticTest st = new StaticTest();,这里初始化对象就是默认运行构造方法,接着输出3,然后输出a=0,b=0,这个时候还没有初始化变量值,默认就是0,然后再运行static静态代码块,输出1,再b赋值,c赋值,a赋值,再非静态代码块,然后调用staticFunction();输出4;所以我想到输出结果是:

3
a=0,b=0
1
2
3
4

但实际结果是:

2
0
3
a=110,b=0
1
4

说实话看到这个输出结果,我有点三观崩塌了,差距是在太大了,我的逻辑就是static要比非static先运行,但是正确答案直接先输出非静态代码块的2?,我查了很多的资料,在jvm基础第三节: <clinit>()<init> 方法中找到了原因,实际的运行步骤应该是这样的:

1.在main方法入口之前会调用 方法,这样就会到static StaticTest st = new StaticTest(),这里就要注意了,这里是对象的初始化,这里就开始调用方法,它运行的是非静态方法和成员变量的赋值;

2.就会运行非静态代码块,先输出2,然后输出c,但是此时c还是0,所以输出0;

3.运行成员变量的初始化,将a赋值为110

4.接着执行构造方法,输出3,a=110,b=0

5.方法运行完之后在继续运行方法,执行静态代码块,c=0,输出1

6.执行staticFunction();输出4

上面过程主要有一点就是虚拟机首先执行的是类加载初始化过程中的 <clinit>() 方法,也就是静态变量赋值以及静态代码块中的代码,如果 <clinit>() 方法中触发了对象的初始化,也就是 <init>() 方法,那么会进入执行 <init>() 方法,也就是对象构造时用以初始化对象的,构造器以及非静态初始化块中的代码。,执行 <init>() 方法完成之后,再回来继续执行 <clinit>() 方法。

可能有点晕,我也是想了很久才想通的,不知道大家有没有get到我都的点哈。

关于类加载的过程也就是这么多了,我这里在上一张图来帮助大家记忆,其实理解了每个阶段的目的,基本上不需要记忆就能够把相关的步骤说出来的

图来自于网络:

RbNqht.png

下一篇我们讲讲类加载器吧,因为对于连接和初始化阶段都是JVM内部已经封装好的,开发人员是不能干涉的,唯一能够干涉的就是加载这个过程了,类加载器就是加载过程的核心。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值