类加载机制


一、类加载机制

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验转換解析初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类的加载机制可以分为加载-链接-初始化三个阶段,链接又可以分为验证、准备、解析三个过程,并且加载、链接、初始化的各个阶段并不是彼此独立,而是交叉进行,比如会在一个阶段执行的过程中调用/激活另外一个阶段。
在这里插入图片描述

1、加载

加载指的是把从各个来源得到的class字节码文件通过类加载器装载入内存中,并且会在堆内存中的方法区生成一个代表这个类的 java.lang.Class 对象,作为这个类的数据请求入口。这里需要了解两个知识点:

  • 来源:一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译

  • 类加载器:从JVM的角度,类加载器是分为了启动类加载器和其他类加载器(包括扩展类加载器,应用类加载器,以及用户的自定义类加载器)两种

    • 启动类加载器(Bootstrap ClassLoader):
      Bootstrap 类加载器负责加载< JAVA_HOME >/lib/ rt.jar 中的 JDK 类文件,它是所有类加载器的父加载器,一般是C++实现的,我们日常用的Java类库比如String, ArrayList等都位于该包内。Bootstrap 类加载器没有任何父类加载器,如果调用 String.class.getClassLoader(),会返回 null,任何基于此的代码会抛出 NUllPointerException 异常。

    • 扩展类加载器(Extension ClassLoader):Extension 类加载器将加载类的请求先委托给它的父加载器,也就是Bootstrap,如果没有成功加载的话,再从 jre/lib/ext 目录下或 java.ext.dirs 系统属性定义的目录下加载类。Extension 由加载器sun.misc.Launcher$ExtClassLoader 实现。

    • 应用程序类加载器(Application ClassLoader):默认的类加载器,是ClassLoader#getSystemClassLoader()的返回值,故又称为系统类(System)加载器,实现类是sun.misc.Launcher$AppClassLoader。它负责加载应用程序的类,包括自己写的和引入的第三方法类库,即所有在类路径中指定的类。

    • 自定义类加载器(User ClassLoader):如果以上类加载起不能满足需求,可自定义类加载器。比如说App安全防护时加的壳,如果需要对自己的代码做防护,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。几种类加载器的关系如下:

在这里插入图片描述

延伸:每个类加载器都拥有一个独立的类名称空间,它不仅用于加载类,还和这个类本身一起作为在JVM中的唯一标识。所以比较两个类是否相等,只要看它们是否由同一个类加载器加载,即使它们来源于同一个Class文件且被同一个JVM加载,只要加载它们的类加载器不同,这两个类就必定不相等。

2、链接

1.验证

主要是对一些词法、语法进行规范性校验,避免对 JVM 本身安全造成危害。

2.准备

准备阶段主要是为类变量分配内存,因为这里的变量是由方法区分配内存的,所以仅包括类变量而不包括实例变量,后者将会在对象实例化时随着对象一起分配在Java堆。 比如 static int a=1,会被初始化成成 a=0;如果是 static double a =1,则会被初始化成 a=0.0,final static tmp = 1, 那么该阶段tmp的初值就是1。

3.解析

将常量池内的符号引用替换为直接引用的过程。在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

3.初始化

对类的静态变量和静态块中的变量进行初始化。如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
初始化阶段和准备阶段的区别是准备阶段的静态变量赋初始零值,而初始化阶段会根据Java程序的设定去初始化类变量和其他资源。

  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
  • 在遇到new、getstatic、putstatic或invokestatic这4个字节码指令时必须立即对类进行初始化

二、双亲委派模型

简单来说包含如下几步:

1.判断是否已经加载过了,加载过了,直接返回Class对象,一个类只会被一个ClassLoader加载一次;

2.如果没有被加载,先让父ClassLoader去加载,如果加载成功,返回得到的Class对象;

3.在父ClassLoader没有加载成功的前提下,自己尝试加载类;

为什么要先让父ClassLoader去加载呢?其实这样可以避免Java类库被覆盖的问题,比如用户程序也定义了一个类java.lang.String,通过双亲委派机制,java.lang.String只会被Bootstrap ClassLoader加载,避免自定义的String覆盖Java类库的定义。

三、类加载顺序

看一下以下程序的执行顺序:

class A{
    public int i = method();
    public static int j = method2();
    public  A(){
        System.out.println(1);
    }

    public int method(){
        System.out.println(2);
        return 2;
    }

    public static int method2(){
        System.out.println(3);
        return 3;
    }
}

class B extends A {
    public int m = method3();
    public static int n = method4();
    public int t = 0;

    public B() {
        System.out.println(4);
    }

    public int method3() {
        System.out.println(5);
        return 5;
    }

    public static int method4() {
        System.out.println(6);
        return 6;
    }
}

class People{

}
 public static void main(String[] args) {

  System.out.println(7);
   A a = new TestDemo2();
   A a1 = new B();
 }

前面在讲初始化时提到当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类,我们会进行类的初始化。在代码中只有一个构造方法,但实际上Java代码编译成字节码之后,是没有构造方法的概念的,只有类初始化方法 和对象初始化方法 。

  • 类初始化方法:编译器会按照其出现顺序,收集类变量的赋值语句、静态代码块,最终组成类初始化方法。类初始化方法一般在类初始化的时候执行。
  • 对象初始化方法:编译器会按照其出现顺序,收集成员变量的赋值语句、普通代码块,最后收集构造函数的代码,最终组成对象初始化方法。对象初始化方法一般在实例化类对象(也就是new)的时候会立即执行。

因此上面程序的最终输出为:

3
7
2
1
6
2
1
5
4
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值