JVM学习整理——类加载器详解
编辑类加载器概述
Java类加载器是JVM的一部分,它主要负责动态的将Java类加载到Java虚拟机的内存当中。
类加载器加载过程
类加载器加载过程主要分为三个阶段,分别是加载阶段,链接阶段,初始化阶段。其中链接阶段也分为三个步骤,分别是验证,准备,解析。(这边参考较多,当笔记先看吧~)
- 加载 首先通过类的全限定名称获取此类的一个二进制的字节流。然后会在内存中生成一个java.lang.Class对象,作为方法区这个类各个数据的访问入口。
- 链接 链接分为三个阶段。 验证 主要用于确保Class文件的字节流中包含信息符合当前虚拟机的要求,保证被加载类的正确性。主要有四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。 准备 为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。 解析 主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。
- 初始化 类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
类加载器分类
类加载器主要分为两类,一类是引导类加载器(BoostrapClassLoader),还有一类是自定义类加载器(User-defined ClassLoader),这边注意一下,只要是派生于抽象类ClassLoader的类加载器都属于自定义的类加载器的范围。下图所示是我们使用的三个最常见的类加载器,也进行了整理。
引导类加载器
- 引导类加载器(BoostrapClassLoader)主要由c/c++实现,嵌套在JVM内部。
- 主要用于加载JVM的核心类库,比如JAVA_HOME/jre/lib/rt.jar、resources.jar等等。
- 引导类加载器没有父类的加载器,它是拓展类以及系统类加载器的父类。
- 为了安全起见,它知会加载指定包名开头的一些类,比如java,javax等等。
拓展类加载器
- 由Java编写,使用的是sun.misc.Launcher$ExtClassLoader。派生于ClassLoader。
- 父类加载器是引导类加载器。
- 从java.ext.dirs的系统属性的指定目录去加载类库,或者JAVA_HOME/jre/lib/ext子目录去加载类库。这边要注意的是,如果用户的类也放在这些目录下面,同样会被加载。
系统类加载器
- 由Java编写,使用的是sun.misc.Launcher$AppClassLoader。派生于ClassLoader抽象类。
- 父类的加载器是拓展类加载器。
- 主要负责加载classpath或者系统变量java.class.path下指定的类库。
- 它是默认的类加载器,一般Java应用程序都是通过系统类加载器来加载的。
- 可以通过ClassLoader.getSystemClassLoader()获得。
下面用代码简单演示一下类的加载器。 这边是简单的获取各个类加载器的一些代码,根据运行结果可以看到相对应的类加载器的结构关系。
@Test
public void testGetClassLoader() {
// 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// 通过classloader.getParent()方法获取系统类加载器的父类加载器,拓展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);
// 获取拓展类系统加载器,启动类加载器,注意启动类加载器输出为null
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);
}
@Test
public void testClassLoader2() {
// 启动类加载器 输出所有加载的jar信息
System.out.println("--------启动类加载器-----------");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL url:urLs) {
System.out.println(url.toExternalForm());
}
// String类是rt.jar下面的一个类,它的classloader是引导类加载器
ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader);
System.out.println("--------拓展类加载器-----------");
// 扩展类加载器 输出加载的目录信息
String property = System.getProperty("java.ext.dirs");
String[] split = property.split(":");
for (String s:split) {
System.out.println(s);
}
// AWTEventMonitor是拓展类加载的一个类,可以看到它的类加载器是ExtClassLoader
ClassLoader classLoader1 = AWTEventMonitor.class.getClassLoader();
System.out.println(classLoader1);
System.out.println("--------系统类加载器-----------");
ClassLoader classLoader2 = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader2);
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// 这边获得的类加载器是一样的,都是系统类加载器
System.out.println(systemClassLoader);
}
testGetClassLoader运行结果 testClassLoader2运行结果
双亲委派机制
最后说一下双亲委派机制,其实这个机制也很简单,下面简单整理一下。
- 首先,类加载器在收到加载类的请求的时候并不会立即去加载这个类,而是一层一层向上委托,交由其父类加载器去执行。
- 如果父类的加载器还存在父类加载器,就会继续向上委托,直到没有父类加载器了,也就是到了引导类加载器。
- 之后再由顶向下开始去加载这个类。如果父类加载器可以完成对此类的加载,那么就加载类,返回结束,如果不能的话,再往下去找子类加载器去加载类,依次类推。最终完成类的加载,这个就是双亲委派机制。
双亲委派机制图示
结语以及参考
这边JVM的类加载器的整理就先这样了,这个当做自己学习的一个汇总整理吧,后续还会继续进行JVM知识点的梳理。 博客的代码都在我个人的代码仓上面,代码仓地址:https://github.com/yzh19961031/blogDemo/tree/master/jvm
然后这边参考的是尚硅谷JVM全套教程(宋红康详解java虚拟机),B站地址:https://www.bilibili.com/video/BV1PJ411n7xZ?p=35