JAVA面向对象-----类加载机制

本文详细阐述了Java类加载机制,包括加载、验证、准备、解析和初始化等阶段,重点讲解了何时类会被初始化,以及类加载器的作用和分类。通过实例演示了类加载的动态特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类加载机制介绍

Java虛拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虛拟机的类加载机制。与那些在编译时需要进行连接的语言不同,在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略让Java语言进行提前编译会面临额外的困难,也会让类加载时稍微增加一些性能开销,但是却为Java应用提供了极高的扩展性和灵活性,Java天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparation)、解析( Resolution)、初始化( Initialization)、使用(Using)和卸载(Unloading) 七个阶段,其中验证、准备、解析三个部分统称为连接(Linking) 。这七个阶段的发生顺序如下图所示。

在这里插入图片描述
在上图中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。请注意,这里写的是按部就班地“开始”,而不是按部就班地“进行”或按部就班地“完成”,强调这点是因为这些阶段通常都是互相交叉地混合进行的,会在一个阶段执行的过程中调用、激活另一个阶段。

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过以下三个步骤来对该类进行初始化。
在这里插入图片描述
加载

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象。

链接

将Java类的二进制代码合并到JVM的运行状态之中的过程。

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题;
  • 准备:正式为类变量(static) 分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配;
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

初始化

  • 执行类构造器 ()方法的过程。类构造器< clinit> ()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器 是构造类信息的,不是构造该类对象的构造器)。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  • 虚拟机会保证一 个类的< clinit > ()方法在多线程环境中被正确加锁和同步。

通过下面的例子理解类加载的过程:

public class test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("m的值:"+A.m);
    }
}
class A {
    static {
        System.out.println("A类静态代码块初始化!");
        m = 300;
    }
    static int m = 100;

    public A() {
        System.out.println("A类的无参构造初始化!");
    }
}

运行结果:在这里插入图片描述
解释:
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。至于是否要触发子类的加载和验证阶段,这点取决于虚拟机的具体实现。

加载

“加载”(Loading)阶段是整个“类加载”( Class Loading)过程中的一个阶段,大家不要混淆这两个看起来很相似的名词。在加载阶段,Java虛拟机需要完成以下三件事情:

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

什么时候会发生类初始化?

类的主动引用(一 定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类;
  • new一个类的对象;
  • 调用类的静态成员(除了final常量)和静态方法;
  • 使用java.lang.reflect包的方法对类进行反射调用;
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类。

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化;
  • 通过数组定义类引用,不会触发此类的初始化;
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)。

参考代码:

public class test06 {
    static{
    System.out.println("Main类被加载!");
}
    public static void main(String[] args) throws ClassNotFoundException {

        //产生类的引用方法
        //主动引用
        Son son = new Son();

        //反射也会产生主动引用
        Class.forName("testReflection.Son");

        //不会产生类的引用方法
        //子类调用父类的变量
        System.out.println(Son.b);

        //定义一个数组,只是开辟空间
        Son[] array = new Son[10];

        //调用子类的常量池中的常量
        System.out.println(Son.M);
    }
}

class Father {
    static int b = 2;
    static {
        System.out.println("父类被加载!");
    }
}
    class Son extends Father {
        static {
            System. out. println("子类被加载!");
            m =300;
        }
        static int m =100;
        static final int M = 1;
}

类加载器的作用

类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成-个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存: 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

在这里插入图片描述
JVM规范定义了如下类型的类加载器:

  • 引导类加载器:用C++编写的, 是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取。
  • 扩展类加载器:负责jre/ib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库。
  • 系统类加载器:负责java - classpath或-D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器。
public class test07 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取系统类的加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //获取系统类加载器的父类加载器-->扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        //获取扩展类加载器的父类加载器-->根加载器(C/c++)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
        //测试当前类是哪个加载器加载的
        ClassLoader classLoader = Class.forName("testReflection.test07").getClassLoader();
        System.out.println(classLoader);
        //测试JDK内置的类是是谁加载的
        classLoader = Class.forName("java.lang.Object ").getClassLoader();
        System.out.println(classLoader) ;
    }
}

运行结果:在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值