Java编译原理--类加载过程

本文详细介绍了Java类加载的过程,包括加载、验证、准备、解析和初始化五个阶段。类加载的触发时机包括主动引用和被动引用两种情况,虚拟机规范对初始化阶段的触发条件有严格规定。加载阶段涉及字节流获取、存储结构转换和Class对象创建。验证阶段确保字节流符合规范,保证虚拟机安全。准备阶段为类变量分配内存并设置初始值。解析阶段将符号引用转换为直接引用,涉及类、接口、字段和方法的解析。初始化阶段执行()方法,完成静态变量和静态代码块的初始化。
摘要由CSDN通过智能技术生成

 

Java语言在刚刚诞生的时候提出过一句著名的口号“一次编写,到处运行”,这句话充分的表达了开发人员对于冲破平台界限的渴望,也解释了Java语言跟平台无关的设定。

一、 概述

上一篇文章介绍了class文件的存储细节,class文件包括了类的各种描述信息,但是Java程序的运行需要在内存中实现,
那么虚拟机是如何加载这些class文件的?class文件中的静态结构是如何转换成实际的存储结构的?内存分配是如何完成的?这些都是本篇文章要讨论的内容。

虚拟机将类的描述文件class文件加载到内存,并且进行安全校验、数据类型解析、内存分配以及初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程就是虚拟机的类加载机制。与解释执行语言不通,Java语言是编译型语言,类型的连接(即加载、连接、初始化过程)是在程序运行期进行的,这样就可以在程序运行期间动态的加载一些内容,这种形式虽然会增加系统的运行开销,但是可以让程序设计更加的灵活。 

 二、类加载的时机

一个类从加载到内存开始,一直到被卸载结束,它的整个生命周期包括加载、连接(验证、准备、解析)、初始化、使用、卸载阶段,其中连接阶段包括验证、准备和解析过程,这几个过程的发生顺序如下图所示:

 


          什么时候触发类的加载动作呢?Java虚拟机规范并没有强制规定类加载时机,这个情况需要具体的虚拟机进行自由实现,例如Tomcat再启动时,会启动引导类加载器、拓展类加载器、通用类加载器和应用类加载器,引导类加载器、拓展类加载器和通用类加载器首先加载和初始化一些类(jvm所需类、Tomcat所需类、及一些通用类),其余的类是收到请求时才进行类的加载操作。
    虽然虚拟机没有明确说明类加载的时机,但是对于初始化阶段,虚拟机规范给了严格规定,有且只有以下几种情况必须立即对类进行初始化:
    1、遇到new、putstatic、getstatic及invokestatic这4条字节码指令时,如果类没有初始化,则立即进行初始化,这4个命令分别代表实例化一个类、设置&读取一个静态字段(没有被final修饰)、调用类的静态方法;
    2、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化;
    3、当初始化一个类的时候,发现其父类没有初始化;
    4、当虚拟机启动时,需用将执行启动的主类(有main()方法的那个类)进行初始化;
   5、当使用动态语言时,如果一个java.lang.invoke.MethodHandle实例最终的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic句柄时,并且这个句柄对应的类没有初始化。
    对于这几种需要初始化的场景,虚拟机规范给了很严格的规定词汇"有且只有",这几种场景称为对类的主动引用,除此之外,其他对类的引用称为被动引用,下面举几个被动引用的例子(来源:深入理解Java虚拟机):

父类代码: 

package com.jvm.classloader;

public class SuperClass {
	static {
		System.out.println("SuperClass init");
	}
	public static String name = "123";
}

子类代码: 

package com.jvm.classloader;

public class SubClass extends SuperClass {
	static {
		System.out.println("SubClass init");
	}
}

测试类:

package com.jvm.classloader;

public class Client {

	public static void main(String[] args) {
		System.out.println(SubClass.name);
	}

}

输出结果:


    输出结果为"SuperClass init"和"123",因为静态变量没有使用final修饰,所以这个字段在初始化之前没有被赋值,需要在初始化阶段赋值,初始化阶段会调用类的类构造器函数<clinit>()方法,这时候静态代码块会执行。

修改后的父类代码:

package com.jvm.classloader;

public class SuperClass {
	static {
		System.out.println("SuperClass init");
	}
	public final static String name = "123";
}

输出结果:


    输出结果只有"123",跟例一不同,使用final修饰后,静态变量在经过编译后,class文件会字段表的attribute_info表中添加ConstantValue属性,这个字段标识这个字段可以在类加载的准备阶段赋值,不需要在类加载的初始化阶段的<clinit>()方法中赋值,所以不会执行静态代码块。
测试代码:

package com.jvm.classloader;

public class Client {

	public static void main(String[] args) {
		SubClass[] subClasses = new SubClass[10];
		System.out.println(subClasses);
	}

}

输出结果:


    输出结果为"[Lcom.jvm.classloader.SubClass;@15db9742",没有任何静态代码块的输出结果,这个数组并不是由用户代码产生,是由虚拟机自动生成的,这个类代表了元素类型为 "Lcom.jvm.classloader.SubClass"的一维数组,数组中的属性和方法都在这个类中,这个类继承自java.lang.Object类。

三、类加载的过程

接下来讨论类加载的详细过程,包括加载、验证、准备、解析和初始化阶段。

 3.1 加载

    "加载"是"类加载"这个过程的一个阶段,是 “类加载”过程中最先开始进行的操作,加载阶段,虚拟机需要完成三件事:
    1、 根据类的全限定名获取定义此类的二进制字节流;
    2、 将这个字节流代表的静态存储结构转换为方法区的运行时数据结构;
    3、 在方法区中为这个类生成一个java.lang.Class对象,作为方法区这个类的访问入口。


    Java的虚拟机规范并没有规定从哪里获取、怎样获取二进制字节流,这个阶段也是用户参与度最高的阶段,用户可以根据二进制文件的不同形式在自定义类加载器控制字节流的获取方式,比如成熟的二进制获取方式和类加载器有:
    1、 从Zip包中读取二进制文件,比如常见的jar、war、ear包;
    2、 运行时动态生成,比如动态代理技术,在java.lang.reflect.Proxy中,使用 ProxyGenerator.generateProxyClass为各种就接口生成形如"*$Proxy"的代理类的二进制字节流;
    3、 从网络中获取,这种场景比较常见的是Applet应用;
    4、 其他文件生产,比如jsp文件生成的二进制class文件;

    ……


    数组的加载跟普通类型加载有所不同,因为数组本身不是通过类加载器加载产生的,数组类是虚拟机自动生成的,但是数组的类型是通过类加载器完成加载的,数组类的创建过程需要遵循以下规则:
    1、 如果数组的类型是引用类型,则引用类型需要使用递归来进行加载,并且数组需要被加载该数组类型的类加载器的命名空间上进行标识;
    2、 如果数组的类型不是引用类型,是基本数据类型,Java虚拟机将会把数组标记为与引导类加载器关联;
    3、 数组的可见性与数组类型的可见性保持一致,如果数组类型是基本类型,则默认可见性为public。

 3.2 验证

验证是类加载的第二个阶段,这个阶段也是持续时间最长(从阶段连续性来说),这个阶段从加载开始进行,一直进行到解析阶段结束。验证是为了保证class文件中的内容是符合虚拟机规范的二进制字节流,防止通过执行一些不安全的二进制字节流而导致虚拟机奔溃。 

Java语言本身是安全的语言,它做了很多的安全校验,比如类型转换、非正常的分支语句跳转、不合法的名称定义等等。但是我们知道,Java虚拟机并不只是执行Java语言编译后的class文件,它可以执行所有的二进制字节流文件(只要符合文件规范),所以我们不能保证其他的文

  • 15
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值