JVM类加载机制理解
#大家好 ,这是我个人的JAVA学习,以及相关知识的一个日记,其中会分享一些我自己的学习心得,以及算是对自己的一个监督,若有不足请大家指正,我们共同学习,共同进步!
这是一篇对于JAVA虚拟机jvm中的类加载器的一些设计以及原理的个人理解,有了这些知识后续会更方便我们去理解并进行jvm的一个优化。
1、类加载过程
首先我们要了解类加载的一个流程,多个java文件经过编译打包生成可运行jar包,终由java命令运行某个主类的main函数启动程序,这里首先需要通过类加载器把主类加载到JVM。 主类在运行过程中如果使用到其它类,会逐步加载这些类。 注意,jar包里的类不是一次性全部加载的,是使用到时才加载。
类加载到使用整个过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
-
加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等
-
验证:校验字节码文件的正确性
-
准备:给类的静态变量分配内存,并赋予默认值
-
解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。
-
初始化:对类的静态变量初始化为指定的值,执行静态代码块
上面提到jar包的类并不是一次性加载,是使用时才加载,那就要涉及到类加载器方面的知识。
2、类加载器和双亲委派机制
在java中,类加载器有以下几种:
启动类加载器:负责加载支撑JVM运行的位于jre目录下lib的核心类库,比如rt.jar。
拓展类加载器:负责加载支撑JVM运行的位于jre目录下ext的拓展目录中的jar类包。
应用程序类加载器:负责加载ClassPath 路径下的类包,主要加载我们自己写的那些类
自定义类加载器:负责加载用户自定义路径下的类包
其中启动类加载器以及拓展类加载器会在项目启动的时候,里面的类包就会被加载到jvm里面去,而应用程序类加载器和自定义类加载器里我们写的那些类,只有在被调用的时候才会加载。
接下来我们试下,下面代码会打印类加载器名称。
public class TestJDKClassLoader {
public static void main(String[] args){
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().get Name());
}
}
运行结果:
null //启动类加载器是C++语言实现,所以打印不出来
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader
注意的是String这个类属于要被启动类加载器加载,但是类加载器其实也是一个类也是需要被加载的,所以底层的启动类加载器使用c语言实现的,因此这里没法打印。
至于如何自定义加载器类,我们只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,这里先列大体逻辑 :
- 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载, 直接返回。
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由 父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器 的findClass方法来完成类加载。还有一个方法是findClass,默认实现是抛出异常,所以我们自定义类加载器主要是重写 findClass方法。
至于重写方法的具体实现这里就不展开了,接下来是说下双亲委派机制的设计意义。
-
沙箱安全机制:比如我们自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改 。
-
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性