Java类加载机制

2019-01-27  本文已影响0人  左大人

序言

今天我们聊一聊Java中类加载机制,简单来说,就是程序运行过程中,虚拟机把类加载到内存中,可以给程序使用。
我们先介绍原理,掌握原理之后,我们就应用这个原理,来分析两个具体的例子。

类文件基本结构

我们都知道,程序中的java文件,编译之后,会生成对应的Class文件,Class文件是一组以8字节为单位的二进制流,各数据项按照严格的顺序紧密排列,中间没有任何间隔。
这么说可能有点抽象,我们举个例子:

public class Test {
    public int add(int a, int b) {
        return a + b;
    }
}

经过编译之后得到Test.class文件,然后我们执行下面命令:
hexdump Test.class

image.png

上图是用十六进制来表示Test.class二进制流。每一位排列紧密,都有其含义。
下面,我们介绍一下Class文件中具体包含哪些内容:

知道了Class存储格式细节,那么类是如何加载到JVM中的呢?不急,下面我们会介绍。

扩展:结合上一篇文章JVM内存结构,我们就不难知道,类结构被加载到JVM后存储在JVM方法区中

类加载流程

类的加载是虚拟机通过类的全限定名来获取此类的二进制字节流
我们先看一下,类加载流程图:
[图片上传失败...(image-c9e3f4-1548600350447)]
由图可知,类加载流程有七个步骤,分别是:加载、验证、准备、解析、初始化、使用、卸载。下面依次介绍这几个步骤:

类加载器

类加载器是用来执行类加载动作的角色。

类和类加载器息息相关,判断两个类是否相等,只有在这两个类被同一个类加载器加载的情况下才有意义,否则即使是同一个类,被不同的类加载器加载,他们也不是相等的

类加载器可以分为三类:

既然有这么多加载器,那么加载类的时候会选择什么类加载器呢?
着这个时候,需要提到类加载器的双亲委派模型,流程图如下:


image.png

如果一个类加载器收到加载类的请求,它不会立刻去加载,它会先请求父类加载器,每个层次的类加载器都是如此。层层传递,知道最高层的类加载器,只有当父类加载器反馈自己无法加载这个类,才会由当前子类加载器去加载该类。

为什么要这么做呢?这是为了让越基础的类由越高层的类加载器去加载,如Object类,最后都会传递给最高层类加载器去加载。类的相等性,是由类与类加载器共同决定,这样无论在何种类加载器环境下都是同一个类。相反,如果没有双亲委派模型,每个类加载器都会去加载Object,系统中就会出现多个不同Object类,如此一来系统最基础的行为都无法保证了。

举例分析原理

为了巩固上面的类加载原理,下面给出两个例子,供大家分析。

例子一

public class Book {
    public static void main(String[] args) {
        staticMethod();
    }

    static Book book = new Book();

    public Book() {
        System.out.println("Book构造方法");
        System.out.println("Book的price="+price+",amount="+amount);
    }

    {
        System.out.println("Book中普通代码块");
    }

    int price = 110;

    static {
        System.out.println("Book中静态代码块");
    }

    public static void staticMethod() {
        System.out.println("Book中静态方法");
        System.out.println("Book amount=" + amount);
    }

    static int amount =  112;
}

给各位同学5分钟,写出这个程序输出的内容。
1.,,2,,,3,,,,4,,,,,5
各位同学有答案了吗,正确的答案如下:

Book中普通代码块
Book构造方法
Book的price=110,amount=0
Book中静态代码块
Book中静态方法
Book amount=112

如果你的答案跟这个一样,恭喜你,你对类加载机制已经有了深刻的认识。
下面,我解释一下,答案为什么是这样的:

对于Book类,其类构造器可以表示为:

static Book book = new Book();

static {
  System.out.println("Book中静态代码块");
}

static int amount =  112;

首先执行static Book book = new Book();,这条语句会触发类的实例化,于是JVM执行对象构造器,对象构造器可以表示为:

{
    System.out.println("Book中普通代码块");
}

int price = 110;

public Book() {
    System.out.println("Book构造方法");
    System.out.println("Book的price="+price+",amount="+amount);
}

首先,输出Book中普通代码块,然后给price赋值为110,接着执行对象构造方法,先输出Book构造方法,再输出Book的price=110,amount=0(静态变量amount再准备阶段赋值为0)。
Book对象构造器执行完毕之后,继续执行静态代码块,输出Book中静态代码块,然后给静态变量赋值为112,这时类构造器也执行完毕。
回到入口方法main中,执行staticMethod方法,先输出Book中静态方法,在输出Book amount=112,到这里,整个程序执行完毕。
看了分析之后,有没有一种豁然开朗的感觉呢!!

例子二

class Grandpa {
    static {
        System.out.println("Grandpa静态代码块");
    }

    public Grandpa() {
        System.out.println("我是爷爷");
    }
}

class Father extends Grandpa {
    static {
        System.out.println("Father静态代码块");
    }

    public Father() {
        System.out.println("我是爸爸");
    }

    static int age = 26;
}

class Son extends Father {
    static {
        System.out.println("Son静态代码块");
    }

    public Son() {
        System.out.println("我是儿子");
    }
}

public class SonTest {
    public static void main(String[] args) {
        System.out.println("爸爸的岁数: " + Son.age);
        new Son();
    }
}

理解了第一个例子,相信这个例子就难不倒大家了,直接公布答案吧:

Grandpa静态代码块
Father静态代码块
爸爸的岁数: 26
Son静态代码块
我是爷爷
我是爸爸
我是儿子

解释:程序入口为main方法,首先会初始化SonTest类,而SonTest中并没有内容,所以不用管,直接进入main方法中,调用Son.age,而age是父类中的静态字段,就会直接初始化父类Father,而不会初始化子类(规则:对于静态字段,只有直接定义这个字段的类才会被初始化)。
初始化Father的时候,发现它继承Grandpa,而Grandpa此时也还没有初始化,所以此时先初始化Grandpa。Grandpa类构造器中只有一段静态代码块,会输出Grandpa静态代码块,然后初始化Father构造器,输出Father构造器,接着就会输出main方法中的第一句爸爸的岁数为:26
接着是实例化Son对象,调用Son的对象构造方法,会先调用父类Father对象构造方法,而Father又继承Grandpa,所以先调用Grandpa对象构造方法,所以最后一次输出我是爷爷我是爸爸我是儿子

到这里,相信大家知道怎样分析这类问题。总结一个方法论:

  1. 确定类变量的初始值:在类加载准备阶段,JVM为类变量(静态变量)初始化默认值,如果被final修饰,则会直接初始化用户赋予的值。
  2. 初始化入口方法:进入类加载初始化阶段,JVM会寻找main方法入口,从而初始化main方法所在的类,当需要初始化一个类时,先初始化类构造器,之后初始化对象构造器
  3. 初始化类构造器:JVM按照顺序收集类变量赋值语句,静态代码块,最终组成类构造器,JVM执行这个类构造器。
  4. 初始化对象构造器:JVM按照顺序收集成员变量赋值语句,普通代码块,最后收集对象构造方法,经他们组成对象构造器,最终由JVM执行。

总结

本文先介绍类加载机制的原理,然后举例2个,帮助大家应用原理来分析具体场景,最后总结一套方法论,如果遇到同样的问题,可以使用这套方法论来解决。希望对大家有帮助。

上一篇 下一篇

猜你喜欢

热点阅读