Java 类加载器

一、什么是类加载器:
类加载器是一个用来加载类文件的
Java 源代码通过javac编译器编译成类文件,然后jvm来执行类文件中的字节码来执行程序,类加载器负责加载文件系统、网络或其他来源的类文件。
Java 类加载器的作用就是在运行时加载类。Java 类加载器基于三个机制:委托、可见性、单一性。
委托机制:是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么子类再加载它
可见性:子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类
单一性:指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类

二、java 类装载方式:

  1. 隐式装载,程序在运行过程中碰到通过new 等方式生成对象时,隐式调用类加载器加载对应的类jvm中。
  2. 显示装载,通过class.forName()等方法,显示加载需要的类。
System.out.println("ClassLoaderDemo.class.getClassLoader():" + ClassLoaderDemo.class.getClassLoader());
		try {Class.forName("classLoader.ClassLoaderDemo",true,ClassLoaderDemo.class.getClassLoader().getParent());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			System.out.println("class not found....");
		}

运行结果如下图:
在这里插入图片描述
类的加载是通过调用java.lang.ClassLoader的loadClass()方法,而loadClass()方法则调用了findClass()方法来定位相应类的字节码。在这个例子中Extension类加载器使用了java.net.URLClassLoader,它从JAR和目录中进行查找类文件,所有以”/”结尾的查找路径被认为是目录。如果findClass()没有找到那么它会抛出java.lang.ClassNotFoundException异常,而如果找到的话则会调用defineClass()将字节码转化成类实例,然后返回。
三、类加载的动态性体现:
一个应用程序总是由n多个类组成,java程序启动时,并不是一次把所有的类加载再运行,总是先把保证程序运行的基础类一次性加载到jvm中,其他类等到jvm用到的时候再加载。这样可以节省内存的开销。

四、java 类装载器分类:
JDK默认提供三种ClassLoader:

  1. Bootstrap加载器是由C++语言写的,在jvm启动后初始化的,负责加载.;%JAVA_HOME%\jre\lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%\jre\classes中的类,是所有类加载器的父类。(lib/rt.jar)
  2. ExtClassLoader将加载类的请求先委托给它的父加载器,即BootStrap类加载器,如果没有成功加载,再从%JAVA_HOME%\jre\lib\ext目录下或者java.ext.dirs系统变量指定的目录下加载类(lib/ext/*.jar)
  3. AppClassLoader类加载器,它负责从classpath 环境变量中加载某些应用相关类或者jar文档。它的父类是ExtClassLoader。(classpath)
    补:classpath定义的是类文件的加载目录,PATH定义的是可执行程序如javac、java等的执行路径。
public static void main(String[] args) {
		ClassLoader cla = ClassLoaderDemo.class.getClassLoader();
		System.out.println(cla);//sun.misc.Launcher$AppClassLoader@2a139a55
		ClassLoader cla1 = cla.getParent();//
		System.out.println(cla1);//sun.misc.Launcher$ExtClassLoader@7852e922
		ClassLoader cla2 = cla1.getParent();
		System.out.println(cla2);//null BootStrap是由c++写的,就java而言,不存在其实体
	}

思考1:为什么需要3个类加载器?
为了分工,各自负责各自的区块,另一方面为了实现委托模型。

思考2:它们之间如何协调工作的?
采用了委托模型机制。即类加载器有载入类的需求时,会先请示Parent使用其搜索路径帮忙载入,如果Parent找不到,那么才由自己依照自己的搜索路径搜索类。

五、类加载器ClassLoder是如何工作的?
类装载器就是寻找类或者接口字节码文件(本质:一个字节数组)进行解析并构造jVM内部对象表示的组件,在java中类装载器把一个类装入JVM,有以下步骤:

  1. 装载:查找和导入Class文件
  2. 链接:其中解析步骤是可以选择的
    (a)检查:检查载入的class文件数据的正确性
    (b)准备:给类的静态变量分配存储空间
    ©解析:将符号引用转成直接引用
  3. 初始化:对静态变量,静态代码块执行初始化工作

Java 装载类使用==“全盘负责委托机制”"全盘负责"指当一个classLoder装载一个类时,除非显示的使用另外一个ClassLoder,不然该类所依赖及引用的类也由这个classLoder载入;“委托机制”==是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。

思考:为什么使用双亲委托模式?
1)可以避免重复加载
2)安全性:如果不使用这种委托模式,那我们可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,双亲委托方式,可以避免这种情况,因为String 已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoder。

六、定义自己的ClassLoder:
java中提供的默认classLoader,只加载指定目录下的jar和class,如果想要加载其他位置上的类和jar,比如:加载网络上的一个class文件,通过动态加载进内存,然后调用这个类中的方法实现业务逻辑。这个时候就需要我们定义自己的类加载器
分两步:
a)继承java.lang.ClassLoader;
b ) 重写父类的findClass方法

思考:为什么偏偏只重写findClass方法?
因为JDK已经在loadClass方法中帮我们实现了ClassLoder搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法会调用findClass方法来搜索类,故只需要重写该方法即可。

七、线程上下文类加载器:
类java.lang.Thread中的方法getContextClassLoder和setContextClassLoder(ClassLoder c)用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoder(ClassLoder c)方法进行设置的话,线程将继承其父线程的上下类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。
前面所说的三种默认加载器并不能解决 Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。

八、类加载器与Web容器:
对于运行在 Java EE容器中的Web应用来说,类加载器的实现方式与一般的Java应用有所不同。不同的Web容器的实现方式也会有所不一样。Tomcat 的每个应用都有一个对应的类记载器实例。该类加载器也使用代理模式,所不同的是首先尝试加载某个类,如果找不到再代理给父类加载器。与一般类加载器的顺序是相反的。这是java servlet规范中的推荐做法,其目的是使得Web应用自己的类优先级高于Web容器提供的类。但是Java核心库的类是不在查找范围之内的,是为了保证Java核心库的类型安全。
简单的原则:
1)每个Web应用自己的Java类文件和使用的库的jar包,分别放在WEB-INF/classes和WEB-INF/lib目录下面
2)多个应用共享的Java类文件和jar包,分别放在Web容器指定的由所有Web应用共享的目录下面
3)当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。
九、思维导图:
在这里插入图片描述
测试:
当在工程里自定义一个java.lang.String.java类,工程会报:
在这里插入图片描述
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐