首页 > 编程知识 正文

java类加载器原理,java静态内部类

时间:2023-05-06 03:00:46 阅读:32576 作者:1737

文末有蛋!

加载类

在虚拟机中,创建对象非常复杂,涉及许多步骤。 首先,从班级道路开始吧。

类由七个阶段组成:从加载到虚拟机内存中,到从内存中卸载,加载、验证、准备、分析、初始化、使用和卸载。 具体包括前五个阶段,如下图所示。

1522982931(1).png

一个. java文件在编译后形成一个或多个class文件。 如果有内部类,则在编译后会生成多个. class文件。 (这些class文件中的信息只有加载到虚拟机中才能运行和使用。

虚拟机将类中的数据从class文件加载到内存中,并对class文件中的数据执行验证、转换、分析和初始化等操作后,虚拟机最终可识别和使用的过程称为“虚拟机类加载”。

接下来,笔者将详细说明其中的几个步骤。

公路自行车

加载是类加载的过程,由类加载器完成。 但是,何时进行“加载”,取决于具体的虚拟机实施,而不是对虚拟机的规格施加限制。

在此阶段,虚拟机必须完成以下步骤:

完全限定类名以获取类的二进制字节流。 检索方法可以从jar包、war包、网络、JSP文件等中检索,从. class文件中检索。

获取字节流后,字节流中的信息将转换为方法区域中的运行时数据结构。

在内存中,生成表示该类的Class对象,作为访问该类的数据入口。 在HotSpot中,Class对象不存在于JVM虚拟机的堆中,而是存在于方法区域中。

在虚拟机中,如果程序正在积极使用类,但该类尚未加载到内存中,JVM虚拟机将执行加载操作,直到初始化完成。

关于班级加载器的话题,将在后面的章节中单独叙述。

验证

加载阶段完成后,JVM虚拟机将开始验证阶段。 这个阶段的目的很简单,很纯粹。 这意味着class文件的内容满足虚拟机的规格要求,在实际运行时不会危及虚拟机本身的安全。

编译javac的时候,你可能会想,是不是已经检查过java程序了,但是即使我写的代码有问题,也不会通过编译阶段哦。

没错。 在javac编译期,进行了代码检查,包括类型转换、代码语法等常见错误。 如果你写的代码有错误,在编译期间会暴露出来。 不修改就无法编译。 当然后续的执行。

但是,编译期完成后,我们可以自由修改生成的class文件。 在这种情况下,如果修改class文件并等待实际代码执行,程序可能会抛出异常并威胁整个系统的可用性。 类加载时需要验证,可以理解为代码的二次验证。

此外,class文件必须从本地Java源代码编译,或者是从网络下载的,或者是从jsp页解析的,或者是您自己用十六进制编写的。 由于它们跳过了javac编译阶段,因此如果不验证Java虚拟机,很可能会加载有问题的字节流,导致系统崩溃,无法确保系统的稳健性。

在实际验证过程中,如果输入的字节流被验证为不符合class文件的格式约束,虚拟机将抛出异常并拒绝执行。

但是,关于如何验证class文件格式,到2011年为止有具体的规格,在Java虚拟机规格(Java SE 7版)中,有文件格式验证、元数据验证、字节码验证、符号参照验证四种类型

文件格式验证:

文件格式验证是指验证class文件的字节流是否符合class文件格式规范。

其中,字节流文件是否以魔数0xCAFEBABE开始; 主、次编号是否在虚拟机处理范围内; class常量池中是否存在不支持的常量类型……等。

该阶段验证的主要目的是确保输入的class文件的字节流得到正确分析并在形式上满足要求。 在此阶段进行验证后,class文件的字节流存储在内存方法区域中,所有后续三个验证都基于方法区域。

元数据验证:

然后进行元数据验证,语义化分析字节码描述的信息,保证其描述内容符合java语言的语法规范。 验证要点如下。 这个班是否有父班; 此类的父类是否继承了不允许继承的类(final限定的类); 此类是否实现了在其父类或接口中请求的所有方法? (抽象类可以不实现。 )类中的字段、方法是否与父类冲突? 不符合规则的重载、覆盖父类中的final字段等)。

这个阶段的验证类似于我们编译时的检查,也类似于java语法上的检查。

字节码验证:

通过数据流和控制流分析,证实程序语义合法、逻辑。

验证符号引用:

主要验证一些引用的真实性和可行性。 例如,如果代码中引用了其他类,则必须检测以下类是否存在,或者如果在代码中访问了其他类的一些属性,则检查这些属性是否可访问:

如果符号引用无法验证,将抛出异常。 编写程序时,经常遇到java.lang.IllegalAccessError、java.lang.NoSuchFieldError和java.lang.NoSuchMethodError

是符号引用验证所产生的。

对于,虚拟机类加载阶段来说,符号引用验证可以省略,如果你所运行的代码都已经被反复使用和验证过,那么你可以考虑省略此步骤,虚拟机也提供了一个参数供我们修改,使用-Xverify:none参数可关闭验证措施,以缩短类加载阶段耗时。

准备

验证阶段完成后,就到了准备阶段。准备阶段,说直白点,就是对类变量设置初始值。这里所说的类变量,是被static修饰的变量,而不是实例变量,这一点要搞清楚。

而准备阶段的初始值,是数据类型的零值。即

public static int value = 123;

对于变量value来说,变量value的初始值是0,并不是123,123在初始化阶段才会被赋值给value。

在Java中,有两大的数据类型,一种是基本类型、另一种是引用类型。对于这两种类型的初始值如下:

image

值得注意的是,初始值也会存在一些特殊情况,如果类的静态变量被final所修饰,那么在准备阶段,该值会直接赋予变量中,而不再需要零值了。

解析

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

Java在编译阶段,会将.java文件编译成.class文件,在生成的.class文件中,static修饰的静态变量就是我们常说的符号引用,但是在编译阶段该符号引用并不知道引用类的实际内存地址(虚拟机还没运行)。直到解析阶段,虚拟机加载了该类才能真正解析到具体的内存地址。

例如:在com.jiaboyan.Person类中引用了com.jiaboyan.Animal类,在编译阶段,Person类并不知道Animal的实际内存地址,因此只能用com.jiaboyan.Animal来代表Animal真实的内存地址。在解析阶段,JVM可以通过解析该符号引用,来确定com.jiaboyan.Animal类的真实内存地址(如果该类未被加载过,则先加载)。

初始化

初始化,是类加载阶段的最后一步。在这步中,才真正开始执行类中定义的Java代码。

在Java虚拟机规范中,如果有以下几种情况时必须立即对类进行“初始化”操作:

(1)遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类没有进行过初始化,则需要先对其进行初始化。生成这四条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候;读取或设置一个类的静态字段(被final修饰除外)的时候;调用一个类的静态方法的时候。

(2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

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

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

在之前的准备阶段,类中定义的static静态变量已经被赋过一次零值。而在初始化阶段,则会调用类构造器来完成初始化操作,为静态变量赋原始值。

此处,需要注意的是类构造器和类构造方法区别。

类构造方法就不用多说了,每创建一次对象,都会自动调用一次;例如:

public class Student{

public Student(){}

}

类构造器类似于一个无参的构造函数,只不过该函数是静态修饰,只会初始化静态所修饰的代码。

public class Student{

public static int x = 10;

public String zz = "jiaboyan";

static{

System.out.println("12345");

}

}

类构造器是由编译器自动收集类中的所有静态变量的赋值动作和静态语句块static{}中的代码合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。综上所述,对于上面的例子来说,类构造器为:

public class Student{

{

public static int x = 10;

System.out.println("12345");

}

}

image

类构造器和类构造方法不同的是,类构造器不需要显式地调用父类类构造器,虚拟机会保证子类类构造器执行之前,父类类构造器已经执行完毕。

image

此外,虚拟机会保证类构造器在多线程环境中被正确执行,如果有多个线程同时去初始化一个类,那么只会有一个线程去执行该类的类构造器,其他线程都需要阻塞的等待。并且,类构造器只会执行一次。

image

可伸缩服务架构-框架与中间件

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。