Java-类加载机制

本文详细介绍了Java类的加载过程,包括加载、链接(验证、准备、解析)、初始化、使用和卸载五个阶段。类加载器是加载过程的关键,包括启动类加载器、扩展类加载器和系统类加载器。此外,文章还探讨了类加载机制,特别是双亲委派模型,以及内部类的分类和加载机制。
摘要由CSDN通过智能技术生成

1. 类的加载过程

类的加载过程分为:加载,链接(验证,准备,解析),初始化,使用,卸载五个部分
类加载过程总览图1:
在这里插入图片描述
类加载过程总览图2:
在这里插入图片描述

1. 加载

  1. 类的加载是由类加载器完成的,通常由JVM提供,也可以自己通过继承ClassLoader积累创建自己的类加载器。通过不同的类加载器,可以从不同来源加载类的二进制数据,通常有以下几种来源:
    1)从本地文件系统加载class文件
    2)从JAR包加载class文件(常见方式),如:JDBC编程时用到的数据库驱动类就是放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
    3)通过网络加载class文件
    4)把一个java源文件动态编译,并执行加载,类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

  2. 加载就是将.class文件通过二进制字节流读入到JVM中,加载阶段JVM必须完成以下三件事:

  • 通过classloader在classpath中获取XXX.class文件,将其以二进制流的形式读入内存
  • 将字节流所代表的的静态存储结构转化为方法区的运行时数据结构
    (即类加载时 class文件常量池 进入 方法区的 运行时常量池 存放)
  • 在内存中生成一个该类的java.lang.Class对象,作为方法区中这个类的各种数据的访问入口

综上:就是加载二进制然后转换成JVM需要的结构,最后生成对应Class对象

2. 链接

类加载之后,系统便生成了一个对应的Class对象,接着进入链接阶段,链接阶段负责把类的二进制数据合并到JRE中。类链接又可以分为验证,准备,解析三个阶段。

2.1 验证

主要确保加载进来的字节流符合JVM规范。
验证目的:确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。
验证阶段完成以下4个阶段检验动作:

  • 文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。如:常量池中是否有不被支持的常量类型。指向常量中的索引值是否存在不存在的常量或不符合类型的常量。主,次版本号是否在当前虚拟机处理范围之内。
  • 元数据验证:对字节码描述的信息进行语义分析,分析是否符合Java语法规范。(是否符合Java语言规范)
  • 字节码验证:分析数据流和控制,确定语义是合法的,主要是针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。(确定程序语法合法,符合逻辑)
  • 符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现无法访问的问题。(确保下一步的解析能正常执行)
2.2 准备

主要是静态变量在方法区分配内存,并设置默认初始值(在初始化阶段会重新赋值为代码中定义的初值)

2.3 解析

是虚拟机将常量池内的符号引用替换为直接引用的过程。可以理解为直接解析为地址性指向关系,如String name=“zhang”,转换为name指向"zhang"的地址。

3. 初始化

初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。当有继承关系时,先初始化父类再初始化子类,所以创建一个子类时其实内存中存在两个对象实例。
(就是执行构造函数的过程,就是为类的静态变量赋真正的的初始值。)
这里要和刚刚说的链接,准备阶段区分一下,如:类中定义:private static int a = 10;执行过程:字节码文件被加载到内存—》链接验证----》链接准备,此时给类变量a分配内存,并赋初值 0----》链接解析----》初始化,此时 把a的真正初值10 赋给a。
子类和父类初始化顺序(针对属性,代码块,构造方法)
在这里插入图片描述

3.1 类初始化时机

只有在对类主动使用的时候才会导致类的初始化,类的主动使用情况有六种:
1)创建类的实例,new一个对象的时候
2)访问某个类或结构的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射,如:Class.forName(“com.shengsiyuan.test”)
5)初始化某个子类对象,则其父类也会被初始化
6)JAVA虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

4 使用

程序之间的相互调用

5 卸载

即销毁一个对象,一般情况下由JVM垃圾回收器完成,代码层面的销毁只是将引用置为null

2. 实例验证1

案例一

package homework.a_innerClass;
public class Student {
   
    static {
   
        System.out.println("Student 类的静态代码块");//		2
    }
    public Student(){
   
        System.out.println("Student 类的构造方法");
    }
    public Student(String methodName){
   //		4
        System.out.println("在"+methodName+"方法中创建Student对象调用的");
    }
}
package homework.a_innerClass;

public class Test {
   

    static {
   
        System.out.println("Test 类的静态代码块");//  		1
    }

    public static void main(String[] args) {
   
        System.out.println("执行main方法");//		3
//		创建学生对象
        Student s1 = new Student("main");
    }
}
//结果:
Test 类的静态代码块
执行main方法
Student 类的静态代码块
在main方法中创建Student对象调用的

注意:该程序是由两个类一起实现
2. 执行流程:
a) 加载main方法所在的类Test
b) 执行Test的静态代码块,输出: Test 类静态代码块
c) 执行main方法,输出: 执行main方法
d) 在使用类创建对象之前,首先要把这个加载到内存,所以接下来先加载Student类
e) 执行Student类的静态代码块:输出:Student类的静态代码块
f) 接下来执行创建对象代码的,执行Student的有参构造:输出:在main方法中创建Student对象调用的
案例二

public class Test {
   
	
	static {
   
		System.out.println("Test 静态代码块");
	}
	public Test(){
   
		System.out.println("Test 构造方法");
	}
	public static void main(String[] args) {
   
		System.out.println("Test main方法");
		Test t = new Test();
	}
}
//执行结果:
Test 静态代码块
Test main方法
Test 构造方法

注意:该程序由一个类完成。

  1. 执行流程
    a) 当前程序启动的时候,会首先加载main方法所在的类
    b) 在加载类的时候,会执行静态代码块中的代码,输出Test 静态代码块
    c) 接下来执行main方法,输出: Test main方法
    d) 最后使用空参构造创建Test对象,执行空参的构造方法,输出Test 构造方法

3. 类的生命周期

在类加载的时候,碰到以下几种情况时,Java 虚拟机将结束生命周期:
1)执行了System.exit()方法
2)程序正常执行结束
3)程序在执行过程中遇到了异常或错误而异常终止
4)由于操作系统出现错误而导致Java虚拟机进程终止

4. 类的加载机制

4.1类加载器

4.1.1 启动类加载器(Bootstrap ClassLoader)

启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现,是虚拟机自身一部分。最顶层类加载器,负责加载JAVA_HOME\li路径下的核心类库或-Xbootclasspath参数指定路径下的jar包加载到内存中注意必须由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也没有用(出于安全考虑Bootstrap启动类加载器只加载包名为java,javax,sun等开头的类)

4.1.2扩展类加载器(Extension ClassLoader)

扩展类加载器是指Sun公司(现已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类负责加载JAVA_HOME\lib\ext目录下或者由系统变量-Djava.ext,dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器

4.1.3系统类加载器(System ClassLoader)

也叫应用程序类加载器(Application ClassLoader),是指sun公司实现的sun.misc.Launcher$AppClassLoader。负责加载系统类路径java -classpath或-Djava.class.path指定路径下的类库,也就是我们常说的classpath路径,开发者可以直接使用系统类加载器,一般情况下(无自定义类加载器)该类加载是程序中的默认的类加载器,使用ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

在Java日常应用程序开发中,类的加载几乎是由上述3中类加载器相互配合执行的,在必要时,还可以自定义类加载器,需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也即当使用该类时才会将他的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它是一种任务委派模式。

4.2 类加载机制分类

JVM的类加载机制主要有三种:全盘负责,双亲委派,缓存机制

4.2.1 全盘负责

就是当一个类加载器负责加载某个Class时,该Class所依赖和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。

4.2.2双亲委派

就是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。即某个特定的类加载器在接到加载类任务时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此项加载任务时才自己去加载

4.2.3 缓存机制

缓存机制会保证所有加载过的Class都会被缓存,当程序中需要使用某个class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class文件对象时系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中,这就是为什么修改了Class之后,必须重新启动JVM程序所做的修改才会生效(运行程序时启动JVM,加载时不启动。对应指令:java Class文件名)。

4.3双亲委派机制原理

在这里插入图片描述

  1. 双亲委派机制工作原理:
    如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己加载,这就是双亲委派任务模式。
  2. 双亲委派机制优势:
  • Java类随着他的加载器一起具备优先级的层次关系,通过这种层级关系可以避免类的重复加载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值