JVM类加载机制

类加载过程

JVM类加载过程包括:加载 -> 链接(验证、准备、解析)-> 初始化。类加载过程由JVM的类加载子系统完成。

加载
加载阶段主要进行以下工作:
1、通过一个类的全限定名来获取定义此类的二进制字节流,即以二进制字节流的形式读取.class文件到方法区;
2、将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构;
3、在java堆中创建一个代表该类的java.lang.Class类对象,作为类在方法区中数据的访问入口。

对于普通java类,JVM会在方法区中创建一个InstanceKlass类的实例,用来保存类的元信息(包括字段、方法、接口、常量池等)。
InstanceKlass是一个c++类,其继承关系如图:
在这里插入图片描述
InstanceKlass:普通的java类在JVM中对应的是InstanceKlass类的实例;
InstanceMirrorKlass:InstanceKlass的子类,对应java.lang.Class;也就是说在堆上创建的java.lang.Class类对象,实际上就是这个c++类实例在堆上的镜像,用来暴露给java程序使用。
ArrayKlass:java数组的元信息用ArrayKlass的子类来表示,包括基本类型数组和引用类型数组。

验证
验证.class文件的字节流中包含的信息是否符合当前虚拟机的要求(比如防止恶意篡改),保障虚拟机的安全。

准备
在方法区中为类变量分配内存并设置初始值。
这里要注意final类型和非final类型的类变量在该阶段设置初始值的方式有所不同。
非final变量:比如public static int value = 1000; 在准备阶段会设置为默认值0,而修改为1000的操作在类的初始化时完成;
final变量:比如public static final int value = 1000; 在准备阶段会直接设置为1000。

解析
将常量池中的符号引用替换为直接引用。

初始化
初始化是类加载过程的最后一步,到这一步,JVM才开始真正执行类中编写的代码。
该阶段主要工作是执行类构造器的<clinit>方法,对静态变量赋值,以及执行静态代码块。
<clinit>方法是由编译器在编译阶段主动收集类中静态变量赋值操作和静态代码块后合并而成的;如果一个类既没有静态变量赋值操作,也没有静态代码块,则编译器不会为其生成<clinit>方法。
<clinit>方法中的指令顺序是由语句在源文件中出现的顺序决定的。

需要对类进行初始化的情况(主动引用):
1、遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时。这里有一个例外:类常量在编译阶段已经将值放入常量池,在读取或操作这一类静态字段时,不需要进行类的初始化。
2、使用 java.lang.reflect 包的方法对类进行反射调用时。
3、初始化一个类时,如果发现其父类没有初始化,则必须先完成父类的初始化。
4、虚拟机启动时,会初始化用户指定的要执行的主类(包含main方法的那个类)。
5、当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

不需要进行初始化的情况(被动引用):
1、编译期间就可以确定值的类常量(static final修饰),不需要在运行时进行初始化,所以访问这一类常量不会触发该类的初始化。
2、子类引用父类的静态字段时,不会触发子类的初始化,只会触发父类的初始化。
3、定义对象数组,不会触发该类的初始化,只会对数组类进行初始化(数组的本质也是一种类)。
4、使用类名获取Class对象时(类名.class),不会触发该类的初始化。
5、使用Class.forName()方法加载指定类时,如果不希望自动初始化,可以使用含boolean initialize参数的方法,将该参数设为false。
6、使用ClassLoader默认的loadClass方法加载类时,不会触发该类的初始化。

类加载器

JVM提供了3种类加载器:
1、启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME/lib 目录下的类库,或者通过-Xbootclasspath参数指定路径下被虚拟机认可的类库。
2、扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME/lib/ext 目录下的类库,或通过 java.ext.dirs 系统变量指定路径下的类库。
3、应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)下的类库。
除了以上类加载器,我们也可以通过继承 java.lang.ClassLoader 类来实现自定义类加载器。
启动类加载器由c++实现,其余类加载器由java实现。
类加载器的层级模型如下(注意层级之间并不是继承关系,而是以组合的方式实现):
在这里插入图片描述

双亲委派机制

JVM通过双亲委派机制进行类加载。除顶层的启动类加载器外,当一个类加载器在收到类加载请求时,不会尝试自己加载这个类,而是将类加载请求委派给上一层的父类加载器,直到委派给顶层的启动类加载器;启动类加载器如果无法加载,就会向下委派给子类加载器来加载,如果子类加载器也无法加载就继续委派给下一层,直到返回给一开始接到类加载请求的那个类加载器;如果自己最终无法加载,则会抛出异常。
双亲委派机制保证了类加载的唯一性,一个类无论使用哪个类加载器进行加载,最终的结果都是唯一的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值