JVM类加载机制简介

类加载

  一个类从被加载到虚拟机内存开始,到卸载出内存,它会经历加载连接(验证,准备,解析)初始化、使用和卸载这5个阶段。前3个阶段又被统称为类加载。

1、加载

  加载是整个类加载过程中的第一个阶段。在加载阶段,JVM需要完成以下3件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流;
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
  • 在内存中生成一个代表这个类的Class对象,作为方法区中这个类的各种数据的访问入口;

  简单点讲就是把由class文件生成的二进制字节流存在方法区,并在中生成一个Class对象指向这个二进制字节流。

2、验证

  验证是连接的第一步,它的任务是确保Class文件的字节流中包含的信息符合*《Java虚拟机规范》*的要求,保证代码在运行时不会对虚拟机产生危害。
  Class文件实际上就是一个二进制文件,开发者自己就可以在二进制编辑器中敲出来,不一定非要通过Java代码编译得到,所以一些Java代码实现不了的事情就可以通过自定义的Class文件实现。自定义的Class可能会有一些威胁虚拟机的操作,所以虚拟机需要先验证所有的Class文件的字节流。
  在验证阶段,JVM需要完成下面这些检查:

  • 验证文件格式,比如看Class文件是否以0xCAFEBABE开头,常量池中常量是否有不支持的常量等等。这个阶段的验证是基于二进制字节流的,文件格式没问题之后,字节流才被允许被放入方法区。后面的3个验证都是基于方法区中的存储结构进行的。
  • 验证元数据,元数据是用来描述类之间关系的数据。这一阶段会验证如是否有父类(java中除了Object类,其他的类都应该有父类),是否实现了其父类中要求实现的方法等等。
  • 验证字节码,即验证类中的方法体,确保类中的方法不会做出危害虚拟机的行为。
  • 验证符号引用,发生在虚拟机将符号引用转化为直接引用的时候(即连接中的解析阶段)。主要是看常量池中通过字符串描述的类,方法,字段是否存在。

  只要通过了验证阶段,就表示加载进来的Class是安全的。在生产环境中,如果能确保全部代码没有问题,可以通过来-Xverify:none关闭验证措施,缩短类加载时间。

3、准备

  准备阶段会为静态变量(类变量)分配内存并附初始值,比如在类中定义了一个用static修饰的int变量i,此阶段就会将i的值设为0

public static int i = 2;

  需要注意的是经过准备阶段,这个i的值是0而非2。真正将i的值变为2是在初始化阶段完成的。
  还有一种情况是当一个变量用final修饰时,会将其设定为所指定的值。下面的i经过准备阶段,值为2

public static final int i = 2;

4、解析

  解析阶段会将常量池内的符号引用替换为直接引用。如果不了解符号引用,可以看下面这个例子。
  首先定义一个最简单类HelloWorld,经过javac HelloWorld.java编译后得到了HelloWorld.class

public class HelloWorld {
}

  用Sublime打开HelloWorld.class就可以得到其16进制格式文件。下面的分析建议结合后面用javap反解析得到的内容一起看。0x000d表示的是常量池的长度,0x0a表示这是一个方法引用CONSTANT_Methodref_info,后面0x0003表示这个方法所在的类是#3常量,0x000a表示这个方法的描述符是#10常量。#3常量是一个类引用常量CONSTANT_Class_info,它引用了#12号常量。#12常量是一个字面量"java/lang/Object"
  同上,#10引用了#4#5,最后得到了字面量"<init>:()v"
  由此就可以知道常量池中第一项就表示了一个Object类中一个无参返回值为空的方法。

这是通过javap -v HelloWorld.class分析得到的Class内容,只截取出了常量池部分。
在这里插入图片描述
  上面分析中CONSTANT_Methodref_infoCONSTANT_Class_info都是符号引用。如果想要详细了解可以学习Class文件格式。符号引用描述了类,方法等在什么包中,长什么样,但是并没有指定这个类在实际的内存中的具体位置。
  直接引用是一个直接指向目标的指针,它指向的是目标在内存的实际位置。
  在解析阶段,就是将常量池中的符号引用解析成了直接引用。经过解析,JVM才能在内存中实际找到目标的位置。

5、初始化

  初始化是类加载的最后一个阶段,这一阶段会执行类构造器<clinit>()方法。<clinit>()方法是编译器自动收集类中的赋值动作和静态代码块中的语句合并产生的。简单讲就是给静态变量赋初始值并执行静态代码块。
  注意这一阶段不会执行构造方法,要分清类加载和实例化。具体验证可以看下面这个例子。我定义了一个类HelloWorld,里面有一个静态语句块和一个构造方法。

  之后在Test类中加载HelloWorld但并没有实例化

  此时控制台只输出了"我是静态方法",即说明初始化阶段只并不会执行构造方法。
在这里插入图片描述

6、类加载小结

  下面对5个阶段各自的工作做一个简单的总结:

加载(Loading):将Class文件生成的二进制流存入方法区,在堆中生成一个Class指向二进制流

验证(Verification):验证字节码是否符合规范

准备(Preparation):给静态变量赋默认值

解析(Resolution):把符号引用转成直接引用

初始化(Initialization):将静态变量赋初始值

7、类加载器

  类加载器实现了类的加载动作,有3种系统提供的类加载器

  • Bootstarp ClassLoader:启动类加载器,负责加载lib目录种的类库,如rt.jar
  • Extension ClassLoader:拓展类加载器,负载加载lib\ext目录种的类库。这个类库中是一些用于扩展JavaSE内容的类
  • Application ClassLoader:应用程序类加载器,负责加载用户类。一般我们自己定义的类就是有这个加载器加载的。

8、双亲委派机制

  用一个例子来说明双亲委派机制。
  现在我们要加载一个自定义的类Test,首先从自定义类加载器开始,如果我们没定义加载器,就是从应用程序类加载器开始。自定义类加载器会先看看自己是否加载过这个类,加载过就直接返回,如果发现自己没加载过,会委派其父加载器应用程序类加载器去加载。同样地,应用程序类加载器也是先看看自己有无加载过,有直接返回,没有就委派给上一级。
  一直委派到启动类加载器,它会到自己的加载范围搜寻这个类,发现没有时,就会开始向下委派。每一级都会到自己的加载范围中寻找这个类,如果在自己的加载范围类,就会进行加载并返回,如果没有,就会委派给下一级去加载。
在这里插入图片描述

9、双亲委派机制源码

ClassLoader类中定义loadClass方法用于加载类,下面的代码为了弄清楚逻辑省去了一些。从下面的代码中就可以清楚地看出类加载机制的实现。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
        // 1、先检查自己是否加载过这个类
        Class<?> c = findLoadedClass(name);
        // 2、如果没有,向上委派
        if (c == null) {
            
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false); 
                } else { 
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                
            }	
		// 3、一直委派到启动类加载器,它会调用findClass方法试图加载这个类
            if (c == null) {
                c = findClass(name);
            }
        }
        // 4、加载成功就返回
        return c;
    }
 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值