【JVM】类的加载过程
文章目录
1.Write Once,Run Anywhere
Write Once,Run Anywhere,每一位Javaer都知道这一句由SUN公司喊出的口号。我们编写同样的一份代码,可以在不同的操作系统中运行而不需要做更多额外的操作,而Java这样的特性,正式通过底层的虚拟机实现的。
下面是从官网弄出来了一张Java架构图:
图中可以看到,JDK是JRE的超集,里面包括了JRE拥有的所有东西,而JVM是JRE的其中一部分。
2.Java文件的编译
我们在初学Java时就已经知道了,编写好的Java文件是通过JDK中的javac指令进行编译的,编译完成后会生成一个.class文件。再使用java指令运行.class文件。
编译的过程和使用到的工具可以了解一下:
Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器-> 注解抽象语法树 -> 字节码生成器 ->Person.class文件
我们此时可以思考一个问题,.class文件是在磁盘中的,既然使用java就可以得到运行的结果,肯定是把它加载到了虚拟机中,那么.class文件中的类、字段、方法等是怎么加载到JVM中的呢?
这就涉及到了类的加载机制。
3.类加载机制
3.1.装载
首先,需要加载类,第一步肯定是要找到需要加载的.class文件在磁盘中的位置吧,我们可以通过完全限定名去找待加载的文件,获取它的字节流,并将这个字节流信息放入到JVM的运行时数据区中去。这个过程称为装载。
在装载的过程中如果遇到了完全限定名完全一样的.class文件,此时会选择加载哪一个呢?
这个可以通过不同的加载器来解决,后面会提到。
3.2.链接
3.2.1.验证
装载阶段只是把类的信息放入到了运行时数据区中,被装载的类是未经验证的,不是随随便便一个文件都能被加载到JVM中的,此时会经过链接阶段的第一个步骤——验证,即验证类的合法性。
3.2.2.准备、解析
类验证通过后,需要对类里面的静态变量进行初始化,但是在初始化之前,JVM需要知道它初始化的到底是什么,怎么才能找到这个变量。
此时就经过链接的第2、3个步骤,准备和解析,为静态变量分配内存空间,并将.class中的一个一个的符号转化为真实的内存地址值,提供访问的入口。
3.3.初始化
经过上面的两个阶段后,自然就进入了初始化阶段,为静态变量赋值,并执行静态代码块。
3.4.类加载小结
类加载的过程:
装载–>链接(验证、准备、解析)–>初始化
装载:查找和导入class文件
- 通过类的完全限定名找到类并获取它的字节流
- 将字节流中类信息保存到方法区
- 在堆中生成一个对应的Class对象
链接:
验证:保证被加载类的正确性,包括文件格式验证、元数据验证、字节码验证、符号引用验证。
准备:给类中的静态变量分配内存,并初始化默认值。
解析:把符号引用转化为直接引用。
初始化:给准备好的静态变量赋值,并执行静态代码块。
4.类加载器(ClassLoader)
在一个项目跑起来的时候,有各种各样的类需要加载到JVM中,有JRE中自带的,有三方中间件的,也有自己编写的代码。这些来自不同地方的类文件,需要不同的类加载器分门别类的加载。
在上面提到的装载阶段中,通过文件的完全限定名将类转化成二进制字节流就是通过类加载器来完成的。
4.1.类加载器的分类
- BootStrapClassLoader:负责加载
$JAVA_HOME$
中 jre/lib/rt.jar中的所有class或 -Xbootclasspath选项指定的jar包。 - ExtensionClassLoader:负责加载Java平台的扩展功能jar包,如
$JAVA_HOME$
中 jre/lib/*.jar或 -Djava.ext.dirs指定目录下的jar包。 - AppClassLoader:负责加载classpath中指定的jar包及Djava.class.path 所指定目录下的类和ar包。
- CustomerClassLoader:通过java.lang.ClassLoader的子类自定义加载class属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。
4.2.双亲委派机制
双亲委派图解
双亲委派的定义
一个类加载器收到一个加载类的请求时,它自己不会首先去加载,而是尝试把这个加载的请求委派给父加载器加载,如果父加载器加载了类,则返回成功。只有父加载器无法完成加载任务时,才会尝试自己去加载。
为什么要有双亲委派机制
前面提到了,如果有完全限定名完全一致的类,如果没有限制的话,会出现重复加载的问题。此外,也可以任意写Java核心API中的类,如Object,通过没有限制的类加载器来篡改核心API。
为了解决这些问题,使用双亲委派机制,越上层的类加载器优先级越高,类加载完成后,如果有相同的类则不会再次加载,避免重复加载的问题。
同时,优先级最高的启动类加载器(BootStrapClassLoader)加载完核心API中的类后,就算有相同的例如Object这样的类,也不会再次加载了,避免了核心API被篡改的问题。
如何破坏双亲委派机制
写一个类继承ClassLoader,重写loadClass方法可以破坏双亲委派。
本文地址:https://blog.csdn.net/qq_38249409/article/details/109609239