Java类加载的过程,熟悉Java流程

本文深入探讨Java虚拟机(JVM)中类的加载过程,包括加载、连接、初始化等关键阶段,以及类加载器的角色和类初始化的时机。解析JVM如何确保类的正确性和安全性,以及类在内存中的分配和初始化流程。

看类的加载过程前先看知道下JVM,类加载器

一、JVM和类:当我们调用 Java 命令运行某个 Java 程序时,该命令将会启动一条 Java 虚拟机进程,不管该 Java 程序有多么复杂,该程序启动了多少个线程,它们都处于该 Java 虚拟机进程里。同一个 JVM 的所有线程、所有变量都处于同一个进程里,它们都使用该 JVM 进程的内存区。

二、类加载器:类加载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在java中类装载器把一个类装入JVM,经过以下步骤:

1、加载:查找和导入Class文件

2、链接:其中解析步骤是可以选择的 (a)检查:检查载入的class文件数据的正确性 (b)准备:给类的静态变量分配存储空间 (c)解析:将符号引用转成直接引用

3、初始化:对静态变量,静态代码块执行初始化工作

 

一、类的加载过程


当Java程序需要使用某个类时,如果该类还未被加载到内存中JVM会通过加载、连接(验证、准备和解析)、初始化三个步骤来对该类进行初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:

1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;

2)如果类中存在初始化语句,就依次执行这些初始化语句,像static代码块,init方法等。

二、类初始化的时机

在类和接口被加载和连接的时机上, Java虚拟机规范给实现提供了一定的灵活性 。但是它严格地定义了初始化的时机 。所有的Java虚拟机实现必须在每个类或接口首次主动使用时初始化 。下面这几种情形必须立即对类进行“初始化”:

1) 遇到 new、 getstatic、 putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化, 则需要先触发其初始化, 生成这4条指令的最常见的Java代码场景是:

a、使用 new关键字实例化对象的时候

b、读取或设置一个类的静态字段的时候(即在字节码中,执行getstalic或putstatic指令时),被final修饰、已在编译期把结果放入常量池的静态字段除外

c、调用一个类的静态方法的时候(即在字节码中执行invokestatic指令时)。
2 ) 当调用Java API中的某些反射方法时, 比如类Class中的方法或者java.lang.reflect包的方法对类进行反射调用的时候, 如果类没有进行过初始化 , 则需要先触发其初始化。

3 ) 当初始化一个类的时候, 如果发现其父类还没有进行过初始化, 则需要先触发其父类的初始化。

4) 当虚拟机启动时, 用户需要指定一个要执行的主类(包合 main()方法的那个类) . 虚拟机会先初始化这个主类。

5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

对于这五种会触发类进行初始化的场景, 虚拟机规范中使用了一个很强烈的限定语:“有且只有 '', 这5种场景中的行为称为对一个类进行主动引用 。 除此之外,所有引用类的方式都不会触发初始化, 称为被动引用。

三、类的加载

加载类加载过程的一个阶段,这两个概念一定不要混淆。在加载阶段, 虚拟机需要完成以下三件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流

2 )将这个字节流所代表的静态存储结构转化为方法区运行时数据结构

3 ) 将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象, 作为方法区这个类的各种数据的访问入口。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:

1) 从本地文件系统加载class文件;
2) 从一个ZIP、 JAR、 CAB或者其他某种归档文件中提取Java class文件,JDBC编程时使用到的数据库驱动就是放在JAR文件中,JVM可以直接从JAR包中加载class文件;
3)通过网络加载class文件,这种场景最典型的应用就是 Applet;
4)把一个java源文件动态编译、并执行加载
5)运行时计算生成, 这种场景使用得最多的就是动态代理接术, 在 java.lang.reflect.Proxy中 , 就是用了ProxyGenerator.generateProxyClass来为特定接口生成形式为“*$Proxy”的代理类的二进制字节流。

四、类的连接


当类被加载后,系统为之生成一个对应的Class对象,接着会进入连接阶段,连接阶段将会负责把类的二进制文件合并到JRE中。类连接分为如下三个阶段:

     验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;
     准备:准备阶段则负责为类的静态属性分配内存,并设置默认初始值,这里的初始化是基本变量的初始化如int=0,boolean=false;
     解析:将类的二进制数据中的符号引用替换成直接引用(符号引用是用一组符号描述所引用的目标;直接引用是指向目标的指针
 

五、验证


验证是连接阶段的第一步, 这一阶段的目的是为了确保 Class文件的字节流中包含的信息符合当前虚拟机的要求, 井且不会危害虚拟机自身的安全。Java语言本身是相对安全的语言,但前面已经说过, Class文件并不一定要求用 Java源码编译而来, 可以使用任何途径, 包括用十六进制编译器直接编写来产生 Class 文件。在字节码的语言层面上, 上述 Java代码无法做到的事情都是可以实现的, 至少语义上是可以表达出来的。虚拟机如果不检査输入的字节流,对其完全信任的话, 很可能会因为载入了有害的字节流而导致系统崩溃 , 所以验证是虚拟机对自身保护的一项重要工作。从整体上看,验证阶段会完成下面四个阶段的检验过程: 文件格式验证、 元数据验证、 字节码验证、符号引用验证。其中包括文件格式验证、元数据验证、字节码验证、符号引用验证

六、准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配 。这个阶段中有两个容易产生混淆的概念需要强调一下, 首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中 。 其次,这里所说的初始值“通常情况”下是数据类型的零值
 

七、解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

八、类的初始化


初始化阶段是类加载过程的最后一步 , 前面的几个阶段, 除了在加载阶段用户应用程序可以通过自定 义类加载器參与之外, 其余动作完全由虚拟机主导和控制。到了初始化阶段, 才真正开始执行类中定义的 Java程序代码。从代码角度,初始化阶段是执行类构造器<clinit>()方法的过程。

JVM初始化一个类一般包括如下几个步骤:

  1. 假如这个类还没有被加载和连接,程序先加载并连接该类;
  2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类;
  3. 假如类中有初始化语句,则系统依次执行这些初始化语句

当执行第二步时,系统对直接父类的初始化也遵循此1、2、3步骤,如果该直接父类又有直接父类,系统再次重复这三步,所以JVM最先初始化的总是java.lang.Object类。



版权声明:本文为优快云博主「爆米花9958」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/xuemengrui12/article/details/82707473

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值