深入理解Java的类加载机制

本文深入探讨了Java的类加载机制,包括加载、验证、准备、解析和初始化五个阶段,详细阐述了每个阶段的任务和特点。同时,文章还介绍了双亲委派模型,解释了为何使用这种模型以及其带来的好处,如防止类的重复加载和命名冲突。
摘要由CSDN通过智能技术生成

一、 编译

  我们知道Java代码在运行时,首先编译器会把我们的代码编译成一个.Class文件,Java中有句话“一次编译,到处运行”,能到处运行的原因和这个Class文件有很大的关系。

二、类加载

  我们知道每一个类都会生成一个.Class文件,这是一个字节码文件,其实就是一串二进制的比特流;一个类的生命周期从被类加载开始,到从内存中卸载结束,一共被分为加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中从加载到初始化这五个过程被称为类加载过程。系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类,当运行某个java程序时,会启动一个java虚拟机进程,两次运行的java程序处于两个不同的JVM进程中,两个jvm之间并不会共享数据。
在这里插入图片描述

加载

  这个阶段首先会获取.Class文件中的二进制字节流,然后会把这个字节流的静态存储结构转化为方法区的运行时数据结构,也就是在方法区中储存这个类的静态成员便变量(这里只是有了一个结构,但是还没有初始化,就是还没有赋值),最后会在内存中生成一个代表整个类的java.lang.Class 对象,这个Class对象就相当于一个模板一样,后续如果我们要new一个类对象时,就会参照这个Class对象,并且还会为访问方法区的静态变量提供一个入口,我们访问静态变量时是通过类名去访问的,其实就是通过这个Class对象去访问的静态变量。

  注意获取二进制字节流时不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类),还可以由程序员自己决定。

验证

  这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。就比如,在Java中不允许发生像访问数组的边界值、将一个类型转型为它并未实现的类型、跳转到不存在的代码行之类的事情,如果有这种情况首先就会编译报错,但是如果出现了bug,通过了编译,或者是通过其他的途径生成了Class文件,如果说虚拟机不检查输入的字节流,很可能因为载入了有害的字节流而导致系统崩溃。因此,验证是虚拟机对自身保护的一项重要工作。验证主要分一下几个方面:

  1. 文件格式验证:主要目的是保证输入的字节流能正确的解析并存储于方法区内,格式上符合描述一个java类型信息的要求。
  2. 元素数据验证:主要目的是对类的元数据信息进行语义校验,保证不存在不符合java语言规范的元数据信息。
  3. 字节码验证:这是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  4. 符号引用验证:符号引用验证可以看做是对类自身以外(常量池中各种符号引用)的信息进行匹配校验,符号引用验证的目的是确保解析动作能够正常执行。

准备

  这个阶段会正式为静态变量分配内存空间并为静态变量赋初值,这个内存分配是发生在方法区中。这个阶段要注意以下几个点:

  1. 这里并没有对实例变量进行内存分配,实例变量将会在对象实例化时随着对象一起分配在JAVA堆中。
  2. 赋初值的意思并不是初始化,赋初值是把变量的值设置为对应类型的默认值,我们知道每一个基本类型的变量都有一个自己的默认值,像int的默认值是0,boolean的默认值为false。
  3. 如果这个静态变量前加了final字段,这个变量在这个阶段就会直接被赋值,举个例子,public static final int v = 8080;这个语句如果不加final字段,准备阶段结束v的值应该为0,但是加了final之后,准备阶段结束v的值就为8080了,因为在编译阶段会为 v 生成一个 ConstantValue 属性,在准备阶段虚拟机就会会根据 ConstantValue 属性将 v赋值为 8080。

解析

  这个阶段JVM会将常量池中的符号引用替换为直接引用,上面我们介绍了在验证阶段验证符号引用就是为这个阶段做铺垫,那么什么是符号引用什么是直接引用呢?

  1. 符号引用:在写代码时,我们对引用这个概念肯定不会陌生,引用引的是一段地址,但是在编译时,JVM却不知道这个地址的具体含义,所以就把这些地址转换成了一些符号地址,就叫符号引用,符号引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。
  2. 直接引用:这就相当于把地址再转换回来,直接引用可以是直接指向目标的指针(这个可以是Class对象中指向静态成员的引用,可以是指向静态方法的引用,也可以是直接指向Class对象的引用),也可以是相对偏移量(比如指向实例变量、实例方法的直接引用都是偏移量),如果有了直接引用,那引用的目标必定已经在内存中存在。

初始化

  初始化是类加载机制的最后一步,这个时候才正真开始执行类中定义的Java程序代码。在前面准备阶段,类变量已经赋过一次系统要求的初始值,在初始化阶段最重要的事情就是对类变量进行初始化,对类变量的赋值有两种方式,一种是声明类变量时指定初始值,第二种是在静态代码块中对类变量赋值,这个阶段主要关注的重点是父子类之间各类资源初始化的顺序。

  在为静态成员赋值时会执行我们的构造器cilent()方法,cilend()方法只会在第一次初始化时执行client()方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证子client()方法执行之前,父类的client()方法已经执行完毕,如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成client()方法

//第一种方法
public class A{
   
    public static int a = 123;  
}

//第二种方法
public class B{
   
	public static int b;
    static{
   
        b = 123;
    }
}

  首先我们要知道类加载的前四个过程是由JVM完成的,最后这个过程是通过代码实现的,那么什么时候会触发初始化这个过程我们就一定要知道:

  1. 在使用new关键字实例化对象时
  2. 通过反射创建类的实例化对象
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值