黑马程序员_Java基础加强笔记:类加载器

时间:2023-02-17 16:36:45
一、什么是类加载器 1)类加载器概述

当程序需要用到某个类时,JVM首先把字节码文件从硬盘上加载进来,并做一些处理之后,这些硬盘的字节码文件就变成了内存字节码。这里加载的具体工作是由类加载器ClassLoader来执行的。Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器:BootStrap,ExtClassLoader,AppClassLoader,每个类负责加载特定位置的类。这3系统默认的类加载器中名字后面带有ClassLoader的是Java语言编写的Java类。即AppClassLoader和ExtClassLoader都是普通的Java类。而Boostrap是C++编写的代码,不是Java语言编写的Java类。

2)系统默认类加载器

(1)Bootstrap类加载器(引导类加载器)
  • Bootstrap类加载器是*类加载器
  • Bootstrap类加载器不是Java类,不需要被别的类加载。是JVM的内嵌类加载器,是JVM内核的一部分。
  • 由于Bootstrap类加载器是JVM内核的一部分,所以Bootstrap类加载器随着JVM的启动而启动。
  • Bootstrap类加载器主要用来加载其他的系统默认的类加载器ExtClassLoader和AppClassLoader类加载器
(2)ExtClassLoader(扩展类加载器)
  • 负责加载打包到Java的jdk安装目录的子目录jre/ext/下面所有的jar包
(3)AppClassLoader类加载器(系统类加载器)
  • 负责加载系统环境变量ClASSPATH指定目录中所有的JAR包
(4)类加载器之间的父子关系和管辖范围图 (5)代码测试ExtClassLoader、AppClassLoader和Bootstrap的继承关系
package cn.itheima;
public class LoaderTest {
public static void main(String[] args) {
ClassLoader classLoader =LoaderTest.class.getClassLoader();

while(classLoader !=null){//只要classLoader不为null就继续循环
System.out.println(classLoader.getClass().getName());//输出当前classLoader对象的名字
classLoader=classLoader.getParent();
}
System.out.println(classLoader);//输出*别类加载器对象名
}
}

输出结果:sun.misc.Launcher$AppClassLoadersun.misc.Launcher$ExtClassLoadernull这里要注意,最后一行输出null是因为,bootstrap类加载器本身不是java类,所以不可能有java对象。这便是循环结束的条件根据输出结果可以验证三个默认类加载器之间的关系。
3)类加载器的委托机制(1)当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托机制。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就会报告ClassNotFoundException异常。
package cn.itheima;
public class LoaderTest2 {
public static void main(String[] args) {
System.out.println("loaded by:"+LoaderTest2.class.getClassLoader().getClass());
}
}
输出结果:loaded by:class sun.misc.Launcher$AppClassLoader因为MyEclipse的具体工程中的.classpath文件会在执行这个工程的某个java文件的时候,把这个工程所在的目录临时添加到CLASSPATH变量。这样名叫ClassLoaderTest工程下的LoaderTest2.java文件对应的字节码实际上是位于AppClassLoader管辖的范围内。当Java的jdk安装目录的子目录 jre/ext/中的某个jar包中某个class文件 ( ExtClassLoader的加载范围) 和CLASSPATH指目录下的某个class文件(AppClassLoader的加载范围)同名的时候,JVM会指派哪一个类加载器进行加载呢?将刚才的LoaderTest2.class文件打成jar包后放入jre\ext文件夹下,再次运行程序。此时运行结果为:loaded by:class sun.misc.Launcher$ExtClassLoader,这样我们就可以验证上述的双亲委托机制。
4)自定义类加载器(1)自定义 类加载器的方法

继承java.lang.ClassLoader然后直接覆盖ClassLoader抽象父类中loadClass方法或者直接覆盖ClassLoader抽象父类中findClass方法。这两种方法都可行,但是如果采取覆盖ClassLoader抽象父类中loadClass方法,就要自己重新编写双亲委托机制,比较麻烦,不推荐。而是应当把自己的类加载逻辑写到findClass方法中。在loadClass()方法的逻辑中如果父类加载失败,就会调用到自定义的findClass方法中的内容。这样就能保证自定义的类加载器符合双亲委托机制。

(2)自定义类加载器的步骤

  1. 新建类继承java.lang.ClassLoader抽象类
  2. 子类重写ClassLoader类的findClass( )方法
  3. 按照自己的逻辑获取字节码数据并将获取到的字节码数据转换为对应的字节数组(byte[ ])
  4. 调用defineClass方法将字节码对应的byte[]数据作为实参传给defineClass方法并将defineClass构建的Class对象返回。
  5. 自定义类加载器的位置

(3)自定义类加载器在类加载器结构中的位置
package cn.itheima;
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader loader =new MyClassLoader();
while(loader!= null){
System.out.println(loader);
loader =loader.getParent();
}
}
}
输出结果:cn.itheima.MyClassLoader@4f1d0dsun.misc.Launcher$AppClassLoader@1372a1asun.misc.Launcher$ExtClassLoader@ad3ba4可见直接继承的类加载器MyClassLoader的parent属性指向AppClassLoader的实例,就是说默认自定义类加载器的parent属性指向AppClassLoader类加载器。因为ClassLoader默认的构造函数如下:
protectedClassLoader() {
this(checkCreateClassLoader(),getSystemClassLoader());
}
默认空构造函数指向了一个带参数的构造函数:
privateClassLoader(Void unused, ClassLoader parent) {
this.parent= parent;
}
所以一般情况下自定义的的类加载器的“父类加载器”(指的是parent指向的实例)都是AppClassLoader(5)自定义类加载器举例:首先将自定义代码放置在特定的文件夹,使其脱离默认类加载器的管辖本例中将自定义类放在F:\\Java\\HeiMa_Advance目录下:LoaderTest代码如下:
package cn.itheima;
public class LoaderTest {
public static void main(String[] args) {

}
public static void show(){
System.out.println("Hello itheima!");
}
}
接下来用自定义的类加载器MyClassLoader对其进行加载:
package cn.itheima;
import java.io.*;
import java.lang.reflect.*;
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {//覆盖find方法
try {//指定类加载器的加载文件夹
FileInputStream fis =new FileInputStream(new File("F:\\Java\\HeiMa_Advance\\",name +".class"));
byte[] classBytes =new byte[1024];
int length =fis.read(classBytes, 0, classBytes.length);
return defineClass(null,classBytes, 0,length);
}catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void LoadWithMyClassLoader(ClassLoader classLoader,String className)
throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException, InstantiationException{
Class<?> clazz = classLoader.loadClass(className);
Object obj = clazz.newInstance();
Method method = clazz.getMethod("show",null);
method.invoke(obj,null);
}
public static void main(String[] args) throws SecurityException, IllegalArgumentException,
ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
ClassLoader myLoader =new MyClassLoader();
String className = "LoaderTest";
LoadWithMyClassLoader(myLoader,className);
}
}