首页 > 编程知识 正文

JVM 类加载机制,jvm的加载过程

时间:2023-05-04 23:46:30 阅读:181423 作者:3255

首先,Throws提出了自己学习期间一直抱有疑问的几个问题:

1、什么是类路? 什么时候进行班级加载?

2、什么是类初始化? 什么时候进行类的初始化?

3、什么时候给变量分配内存?

4、什么时候给变量分配默认初始值? 变量赋值程序何时设定初始值?

5、什么是类加载器?

6、如何创建自定义的类加载器?

首先,编译代码时,将生成一个二进制字节流文件(*.class ),JVM(Java虚拟机)可以识别该文件。 另一方面,JVM将Class文件中的类描述数据从文件加载到内存中,进行数据的验证、变换分析、初始化,最终使这些数据成为JVM可以直接使用的Java型。 将这个简单但实际上复杂的过程从JVM的类加载机制

Class文件中的“类”从加载到JVM内存中到卸载内存有七个生命周期阶段。 类的加载机制包括前五个阶段。

如下图所示。

请注意,其中,加载、验证、准备、初始化、卸载的开始顺序是固定的,只是按顺序开始,进行和结束的顺序不是一定的。 分析阶段可能在初始化后开始。

此外,类加载不需要在程序“第一次使用”时开始,而是允许JVM预加载某些类。 (类加载的时机)

一、类的加载

通常,加载不是指类的加载机制,而是指类加载机制的第一步的加载。 在这个阶段,JVM主要完成三件事。

1 .从类的完全限定名称(包名和类名)中获取定义类的二进制流(Class文件)。 获取方法有jar包、war包、网络获取、JSP文件生成等。

2 .将该字节流为代表的静态存储结构转换为方法区域的运行时数据结构。 这里只是转换了数据结构,没有合并数据。 (所谓方法领域,是为了收纳被加载的类信息、常数、静态变量、编译后的代码的运行时存储器领域)

3、在内存中生成表示这个类的java.lang.Class对象,作为访问方法区域这个类的各种数据的入口。 此Class对象没有规定它位于Java堆内存中。 虽然是对象,但它是存储在方法区域中的特殊对象。

二、类的连接

的加载过程将生成类的java.lang.Class对象,以进入将类的二进制数据合并到javaruntimeenvironment (JRE )中的连接阶段。 班级的连接大致分为三个阶段。

1、验证:验证加载的类是否具有正确的结构,类数据是否符合虚拟机要求,以确保虚拟机的安全。

2、准备:类的静态变量(static filed )为方法区域分配内存并分配缺省初始值(0或null值)。 例如,静态输入a=100;

在准备阶段会为静态变量a分配默认值0。

当实例化类时,典型的成员变量与对象一起分配给堆内存。

另外,静态常数(static final filed )可包含static final int a=666; 静态变量a在准备阶段直接代入666,对于静态变量在初始化阶段进行。

用直接引用替换3、解析:类二进制数据中的符号引用。

三、类的初始化

类初始化是类加载的最后一步,加载阶段除外,用户可以通过自定义类加载器参与。 其他阶段完全由虚拟机主导和控制。 到了初始化阶段才执行Java代码。

类的初始化的主要工作是在静态变量代入程序中设定的初始值。

例如,静态输入a=100; 在准备阶段,a被分配默认值0,在初始化阶段被分配100。

在Java虚拟机规范中为有且只有五种情况必须对类进行初始化

1、用new字节码命令编写

类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候,对应类必须进行过初始化。

2、通过java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则要首先进行初始化。

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

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

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


注意,虚拟机规范使用了“有且只有”这个词描述,这五种情况被称为“主动引用”,除了这五种情况,所有其他的类引用方式都不会触发类初始化,被称为“被动引用”。


被动引用的例子一:

通过子类引用父类的静态字段,对于父类属于“主动引用”的第一种情况,对于子类,没有符合“主动引用”的情况,故子类不会进行初始化。代码如下:

//父类public class SuperClass {//静态变量valuepublic static int value = 666;//静态块,父类初始化时会调用static{System.out.println("父类初始化!");}}//子类public class SubClass extends SuperClass{//静态块,子类初始化时会调用static{System.out.println("子类初始化!");}}//主类、测试类public class NotInit {public static void main(String[] args){System.out.println(SubClass.value);}}输出结果:


被动引用的例子之二:

通过数组来引用类,不会触发类的初始化,因为是数组new,而类没有被new,所以没有触发任何“主动引用”条款,属于“被动引用”。代码如下:

//父类public class SuperClass {//静态变量valuepublic static int value = 666;//静态块,父类初始化时会调用static{System.out.println("父类初始化!");}}//主类、测试类public class NotInit {public static void main(String[] args){SuperClass[] test = new SuperClass[10];}}

没有任何结果输出!

被动引用的例子之三:

刚刚讲解时也提到,静态常量在编译阶段就会被存入调用类的常量池中,不会引用到定义常量的类,这是一个特例,需要特别记忆,不会触发类的初始化!

//常量类public class ConstClass {static{System.out.println("常量类初始化!");}public static final String HELLOWORLD = "hello world!";}//主类、测试类public class NotInit {public static void main(String[] args){System.out.println(ConstClass.HELLOWORLD);}}


下一篇:JVM类加载机制详解(二)类加载器与双亲委派模型


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