类加载机制——双亲委派机制

类加载器分类

类加载器

类加载器(英文:ClassLoader)负责加载 .class 字节码文件,.class 字节码文件在文件开头有特定的文件标识。ClassLoader 只负责 .class 字节码文件的加载,至于它是否可以运行,则由 Execution Engine 决定。

类加载机制——双亲委派机制_第1张图片

JVM 中类加载器分为四种:前三种为虚拟机自带的加载器。

  • 启动类加载器(Bootstrap):使用 C++ 语言编写的类加载器,在Java环境下看不到

    负责加载 JAVA_HOME/lib/.jar 里所有的 class。由 C++ 实现,不是 ClassLoader 子类

  • 扩展类加载器(Extension):sun.misc.Launcher.ExtClassLoader

    负责加载 Java 平台中扩展功能的一些 jar 包,包括 JAVA_HOME/lib/.jar 或 -Djava.ext.dirs 参数指定目录下的 jar 包、以及 JAVA_HOME/lib/ext/classes 目录下的 class。

  • 应用类加载器(AppClassLoader):sun.misc.Launcher.AppClassLoader

    也叫系统类加载器,负责加载classpath中指定的 jar 包及目录中的 class

  • 自定义类加载器:程序员自己开发一个类继承 java.lang.ClassLoader,定制类加载方式

父子关系1:启动类加载器是扩展类加载器的父加载器

父子关系2:扩展类加载器是应用类加载器的父加载器

代码测试

// 1.获取Person类的Class对象
// 2.通过Class对象进一步获取它的类加载器对象
ClassLoader appClassLoader = Person.class.getClassLoader();

// 3.获取appClassLoader的全类名
String appClassLoaderName = appClassLoader.getClass().getName();

// 4.打印appClassLoader的全类名
// sun.misc.Launcher$AppClassLoader
System.out.println("appClassLoaderName = " + appClassLoaderName);

// 5.通过appClassLoader获取扩展类加载器(父加载器)
ClassLoader extClassLoader = appClassLoader.getParent();

// 6.获取extClassLoader的全类名
String extClassLoaderName = extClassLoader.getClass().getName();

// 7.打印extClassLoader的全类名
// sun.misc.Launcher$ExtClassLoader
System.out.println("extClassLoaderName = " + extClassLoaderName);

// 8.通过extClassLoader获取启动类加载器(父加载器)
ClassLoader bootClassLoader = extClassLoader.getParent();

// 9.由于启动类加载器是C语言开发的,在Java代码中无法实例化对象,所以只能返回null值
System.out.println("bootClassLoader = " + bootClassLoader);

双亲委派机制

类加载机制——双亲委派机制_第2张图片

  • 当我们需要加载任何一个范围内的类时,首先找到这个范围对应的类加载器
  • 但是当前这个类加载器不是马上开始查找
  • 当前类加载器会将任务交给上一级类加载器
  • 上一级类加载器继续上交任务,一直到最顶级的启动类加载器
  • 启动类加载器开始在自己负责的范围内查找
  • 如果能找到,则直接开始加载
  • 如果找不到,则交给下一级的类加载器继续查找
  • 一直到应用程序类加载器
  • 如果应用程序类加载器同样找不到要加载的类,那么会抛出ClassNotFoundException异常

 

验证双亲委派机制

实验1

  • 第一步:在与JDK无关的目录下创建Hello.java
public class Hello {
	
	public static void main(String[] args){
		System.out.println("Hello  world");
	}
	
}
  • 第二步:编译Hello.java
  • 第三步:将Hello.class文件移动到$JAVA_HOME/jre/classes目录下
  • 第四步:修改Hello.java
public class Hello {
	
	public static void main(String[] args){
		System.out.println("goodbye");
	}
	
}
  • 第五步:编译Hello.java
  • 第六步:将Hello.class文件移动到$JAVA_HOME/jre/lib/ext/classes目录下
  • 第七步:修改Hello.java
public class Hello {
	
	public static void main(String[] args){
		System.out.println("Tom like jerry");
	}
	
}
  • 第八步:编译Hello.java
  • 第九步:使用java命令运行Hello类,发现打印结果是:Hello  world
    • 说明Hello这个类是被启动类加载器找到的,找到以后就不查找其他位置了
  • 第十步:删除$JAVA_HOME/jre/classes目录
  • 第十一步:使用java命令运行Hello类,发现打印结果是:goodbye
    • 说明Hello这个类是被扩展类加载器找到的,找到以后就不查找其他位置了
  • 第十二步:删除$JAVA_HOME/jre/lib/ext/classes目录
  • 第十三步:使用java命令运行Hello类,发现打印结果是:Tom like jerry
    • 说明Hello这个类是被应用程序类加载器找到的

实验2

  • 第一步:创建假的String类
package java.lang;

public class String {

    public String() {
        System.out.println("嘿嘿,其实我是假的!");
    }

}
  • 第二步:编写测试程序类
    @Test
    public void testLoadString() {

        // 目标:测试不同范围内全类名相同的两个类JVM如何加装
        // 1.创建String对象
        java.lang.String testInstance = new java.lang.String();

        // 2.获取String对象的类加载器
        ClassLoader classLoader = testInstance.getClass().getClassLoader();
        System.out.println(classLoader);
    }
  • 第三步:查看运行结果是null
    • 假的String类并没有被创建对象,由于双亲委派机制,启动类加载器加载了真正的String类

双亲委派机制的好处

  1. 避免类的重复加载:父加载器加载了一个类,就不必让子加载器再去查找了。同时也保证了在整个 JVM 范围内全类名是类的唯一标识。
  2. 安全机制:避免恶意替换 JRE 定义的核心 API

你可能感兴趣的:(JVM,jvm)