大厂Java一面:了解Java虚拟机?讲讲什么是类加载机制大家好,我是发财!今天给小伙伴介绍一下虚拟机中的类加载机制!有不对的地方也可以在评论区留言探讨,也可以转发关注下我以后会长期分享!
虚拟机中的类加载机制
概述
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。
类的生命周期
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用、卸载七个阶段。其中验证、准备和解析三个部分统称为连接。
大厂Java一面:了解Java虚拟机?讲讲什么是类加载机制- 加载:加载是类加载的第一个阶段,这个阶段,首先要根据类的全限定名来获取定义此类的二进制字节流,将字节流转化为方法区运行时的数据结构,在 Java 堆生成一个代表这个类的 java.lang.class 对象,作为方法区的访问入口。
- 验证:这一步的目的时确保 Class 文件的字节流包含的信息符合当前虚拟机的要求。
- 准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都会在方法区中进行分配。仅仅是类变量,不包括实例变量。
<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);">public static int value = 123;
</pre>
- 变量在准备阶段过后的初始值为0而不是123,123的赋值要在变量初始化以后才会完成。
- 解析:虚拟机将常量池内的符号引用替换为直接引用的过程。
- 初始化:初始化是类加载的最后一步,这一步会根据程序员给定的值去初始化一些资源。
什么时候加载
对于什么时候进行类的加载,虚拟机规范中并没有进行强制约束。但是以下几种情况时,必须对类进行初始化(加载、验证、准备则肯定要在此之前完成)。
- 遇到 new,getstatic,putstatic 或 invokestatic 这四条字节码指令时,如果没有初始化则要先触发其初始化。生成这4条指令的 Java 代码场景是:使用 new 关键字实例化对象的时候,读取或者设置一个类的静态字段,或调用一个类的静态方法时。
- 使用 java.lang.reflect 包进行反射调用时,如果没有初始化,则先要进行初始化。
- 当初始化一个类的时候,发现其父类还没被初始化,则需要先触发父类的初始化。
- 虚拟机启动时,用户需要指定一个执行的主类(包含 main 方法的类),虚拟机会先初始化这个主类。
这四种场景称为对一个类进行主动引用,除此之外所有引用类的方式都不会出发初始化。
下面演示两个被动使用类字段的例子,通过子类引用父类的静态字段,不会导致子类初始化:
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);">class SuperClass{
static{
System.out.println("super init");
}
public static int value = 123;
}
class SubClass extends SuperClass{
static{
System.out.println("sub init");
}
}
public class Show{
public static void main(String[] args){
System.out.println(SubClass.value);
}
}
//输出结果
super init
123
</pre>
常量在编译阶段会存入调用类的常量池,本质上没有直接应用到定义常量的类,因此不会使定义常量的类的初始化,**这段代码运行后不会输出 ConstClass init **,因为虽然在 Java 源码中引用了 ConstClass 类中的常量 HELLOWORLD,但是在编译阶段这个值就已经被存到了常量池中,对 ConstClass.HELLOWORLD 的引用实际都转化为了 Show 类对自身常量池的引用了。这两个类在编译成 Class 之后就不存在任何联系了。
<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);">class ConstClass{
static{
System.out.println("ConstClass init!");
}
public static final String HELLOWORLD = "hello world";
}
public class Show{
public static void main(String[] args){
System.out.println(ConstClass.HELLOWORLD);
}
}
//定义常量的类并没有初始化
hello world
</pre>
接口有一点不同,当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有真正使用到了父接口才会初始化。
类加载器
虚拟机设计团队把类加载阶段中的通过一个类的全限定名来获取描述此类的二进制字节流这个动作放到 Java 虚拟机外部去实现,以便让程序自己去决定如何获取所需要的类,这个动作的代码模块称为类加载器。
对于一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,比较两个类是否相等需要在这两个类是由同一个类加载器加载的前提下才有意义。
双亲委派模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。只有当父类加载器反馈自己无法完成这个加载请求(他的搜索范围中没有找到所需的类)时,子类加载器才会尝试去加载。
好处:使用双亲委派模型的好处是,Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,比如 java.lang.Object,它存放在 rt.jar 中,无论哪一个类加载器要加载这个类,最后都是委派给启动类加载器进行加载。
如果不使用双亲委派模型,用户自己写一个 Object 类放入 ClassPath,那么系统中将会出现多个不同的 Object 类,Java 类型体系中最基础的行为也就无从保证。
现在你可以尝试自己写一个名为 Object 的类,可以被编译,但永远无法运行。因为最后加载时都会先委派给父类去加载,在 rt.jar 搜寻自身目录时就会找到系统定义的 Object 类,所以你定义的 Object 类永远无法被加载和运行。
Java 虚拟机的类加载器可以分为以下几种:
大厂Java一面:了解Java虚拟机?讲讲什么是类加载机制- 启动类加载器(Bootstrap ClassLoader):这个类负责将 \lib 目录中的类库加载到内存中,启动类加载器无法被Java程序直接饮用。
- 扩展类加载器(Extension ClassLoader):负责加载 \lib\ext 目录中的类。开发者可以直接使用扩展类加载器。
- 应用程序类加载器(Application ClassLoader):这个类加载器是 ClassLoader 中 getSystemClassLoader() 方法的返回值,所以一般称为系统类加载器。如果没有自定义过加载器,一般情况下这个就是默认的类加载器。
- 自定义类加载器(User ClassLoader):通过自定义类加载器可以实现一些动态加载的功能,比如 SPI。
推荐阅读
不管多忙,每天给自己预留至少半小时的学习时间,拒绝做代码垃圾的搬运工!刚刚入驻简书,有不对的地方可以在评论区留言,觉得不错的朋友希望能得到您的转发支持,同时可以持续关注我,每周定期会分享3到4篇精选干货!
网友评论