概述
准备写一个类加载器相关的系列文章,本篇作为第一篇内容,主要阐述类加载器的作用,同时针对类加载器做一个分类说明。
类加载器的作用
众所周知,我们写的java代码通过前端编译器最终变成class文件,那么磁盘上的class文件是怎么加载到jvm中的呢?答案当然是类加载器ClassLoader。 ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类对应的java.lang.Class对象实例。然后交给Java虚拟机进行链接、初始化等操作。因此,ClassLoader在整个装载阶段,只能影响到类的加载,而无法通过ClassLoader去改变类的链接和初始化行为。
类加载器加载时机
在什么情况下类加载器会加载类,分为两种情况:显示加载和隐式加载。
显示加载
代码中通过调用ClassLoader加载class对象,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加载class对象。
隐式加载
隐式加载是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,那么被引用的类也会被加载到JVM中。
// 隐式加载 Person person = new Person(); //显式加载,并初始化 Class clazz=Class.forName("com.alvin.classloader.Person"); //显式加载,但不初始化 ClassLoader.getSystemClassLoader().loadClass("com.alvin.classloader.Person");
启动参数添加-XX:+TraceClassLoading可以查看类加载器加载的所有类。
类加载器的分类和关系
并不是所有的类都是由同一个类加载器加载的,广义的来说,JVM类加载器分为两大类,分别为启动类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
JAVA虚拟机规范定义,所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。比如jdk内置的扩展类加载器(Extension ClassLoader)和应用类加载器(Application ClassLoader)都属于自定义类加载器,他们之间的关系如下:
- 除了顶层的启动类加载器外,其余的类加载器都应当有自己的“父类”加载器。
- 不同类加载器看似是继承(Inheritance)关系,实际上是包含关系。在下层加载器中,包含着上层加载器的引用。
启动类加载器(Bootstrap ClassLoader)
- 这个类加载使用C/C++语言实现的,嵌套在JVM内部。
- 它用来加载Java的核心库(JAVAHOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。用于提供JVM自身需要的类。也就是说只加载包名为java、javax、sun等开头的类。
- 并不继承自java.lang.ClassLoader,没有父加载器。
- 被启动类加载器加载的类,获取他们的类加载器为null, 也就是说启动类加载器没有这个java类,因为她是C/C++实现的。
System.out.println("**********启动类加载器加载的目录**********"); URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (URL url : urLs) { System.out.println(url.toExternalForm()); } System.out.println("**********java.lang.String的类加载是什么?**********"); ClassLoader classLoader = String.class.getClassLoader(); // null, 启动类加载器获取到的是null System.out.println(classLoader);
输出结果:
扩展类加载器(ExtClassLoader)
- Java语言编写,继承于ClassLoader类, 由sun.misc.Launcher$ExtClassLoader实现。它的父类加载器为启动类加载器。
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
System.out.println("***********扩展类加载器加载目录***********"); String extDirs = System.getProperty("java.ext.dirs"); for (String path : extDirs.split( ";")){ System.out.println(path); } // 从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器 ClassLoader classLoader1 = sun.security.ec.ECKeyFactory.class.getClassLoader(); System.out.println(classLoader1); //sun.misc. Launcher$ExtCLassLoader@1540e19d // 扩展类的父类加载器为启动类加载器 System.out.println(classLoader1.getParent()); // null
输出结果:
系统类加载器(AppClassLoader)
- Java语言编写,继承于ClassLoader类, 由sun.misc.Launcher$AppClassLoader。它的父类加载器为扩展类加载器。
- 它加载环境变量classpath和系统属性java.class.path 指定路径下的类库,比如我们自己写的java类就是由系统类加载器加载的。
- 通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器。
// 自己写的Person类 ClassLoader classLoader = com.alvin.classloader.Person.class.getClassLoader(); System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2 // 应用类加载器的父类是扩展类加载器 System.out.println(classLoader.getParent()); //sun.misc.Launcher$ExtClassLoader@266474c2
以上就是java中内置的一些类加载器,那他们是如何协同工作的呢,这就涉及到一个双亲委派机制,在后面的文章中详细介绍。
类加载器特点
- 类的唯一性,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性。也就是说,比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。
- 可见性,子类加载器可以访问父类加载器加载的类型,反之不行。也就是可以调用AppClassLoader中loadClass加载我们自己写的Person类,但是不能用ExtClassLoader加载我们写的Person类。
- 单一性,已经被父类加载器加载过的类,不会重复加载,可以直接使用。
小结
本文是类加载器的一个入门篇,介绍了类加载器的作用和分类,并对java内置的各个类加载器做了一个说明。在后面,我们详细介绍各个类加载器是如何协同工作,完成类的加载。