JVM类加载机制

 一、类加载器

1、概念

  •  类加载器子系统负责从文件系统或者网络中加载*.class字节码文件
  •  字节码文件开头必须有特定的文件标识, 如果使用二进制文件查看工具打开Java编译得到的字节码文件,会发现,文件开头是:CA FE BA BE 它们都是十六进制数的符号。
  • ClassLoader只负责字节码文件的加载,至于它是否可以运行,则由Execution Engine执行引擎决定的
  • 加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字字面量(这部分常量信息是字节码文件中常量池部分的内存映射) 

2、分类

 

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

中文名称 英文名称 说明 加载范围
启动类加载器 Bootstrap C++ 语言编写,不是 ClassLoader 子类,Java 中为 null $JAVA_HOME/jre/lib/rt.jar
扩展类加载器 Extension sun.misc.Launcher.ExtClassLoader JAVA_HOME/jre/lib/*.jar
-Djava.ext.dirs 参数指定目录下的 jar 包
JAVA_HOME/jre/lib/ext/classes 目录下的 class
应用类加载器 AppClassLoader sun.misc.Launcher.AppClassLoader classpath中指定的 jar 包及目录中的 class<br>以及我们导入的第三方框架的jar包
自定义类加载器 程序员自己开发一个类继承 java.lang.ClassLoader,<br>定制类加载方式

3、父子关系

  • - 父子关系1:启动类加载器是扩展类加载器的父加载器
  • - 父子关系2:扩展类加载器是应用类加载器的父加载器

 这里的『父子关系』并不是通过子类『继承』父类建立的——不是类型上的父子关系。 而是在子加载器中通过parent属性指向父加载器这样建立的——是对象之间逻辑上的父子关系。

4、通过代码查看类加载器 

// 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);

二、双亲委派机制

『双亲委派机制』这个名字不能顾名思义。 在中文语境下,双亲是指父母;但是这里实际上是指『爸爸』和『爷爷』。 所以我觉得应该叫:祖孙三代,比拼啃老机制,更贴切

1、机制简介

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

2、实验1

  • 第一步:在与JDK无关的目录下创建Hello.java
public class Hello {
  
  public static void main(String[] args){
    System.out.println("AAA");
  }
  
}
  • 第二步:编译Hello.java
  • 第三步:将Hello.class文件移动到$JAVA_HOME/jre/classes目录下
  • 第四步:修改Hello.java
public class Hello {
  
  public static void main(String[] args){
    System.out.println("BBB");
  }
  
}
  • 第五步:编译Hello.java
  • 第六步:将Hello.class文件移动到$JAVA_HOME/jre/lib/ext/classes目录下
  • 第七步:修改Hello.java
public class Hello {
  
  public static void main(String[] args){
    System.out.println("CCC");
  }
  
}
  • 第八步:编译Hello.java
  • 第九步:使用java命令运行Hello类,发现打印结果是:AAA,说明Hello这个类是被启动类加载器找到的,找到以后就不查找其他位置了
  •  第十步:删除$JAVA_HOME/jre/classes目录
  •  第十一步:使用java命令运行Hello类,发现打印结果是:BBB, 说明Hello这个类是被扩展类加载器找到的,找到以后就不查找其他位置了
  •  第十二步:删除$JAVA_HOME/jre/lib/ext/classes目录
  •  第十三步:使用java命令运行Hello类,发现打印结果是:CCC, 说明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. 避免类的重复加载:当父类加载器已经加载了某个类时,子类加载器就不再需要重复加载该类,从而避免了类的重复加载问题。

  2. 防止核心API被篡改:由于Java的核心API是由启动类加载器加载的,因此可以保证这些API的安全性和一致性,防止被恶意代码篡改。

  3. 促进类加载器的组织和设计:通过双亲委派机制,Java类加载器形成了一个层次化结构,每个类加载器都有其特定的加载范围和优先级,这种组织和设计方式为Java应用程序提供了更灵活和可扩展的架构。

总之,Java的双亲委派机制可以有效地确保类的安全和一致性,促进类加载器的合理组织和设计,使得Java应用程序具有更高的可靠性、安全性和可扩展性。

 参考文章

相关推荐

  1. JVM机制

    2024-07-10 20:48:05       16 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 20:48:05       5 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 20:48:05       5 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 20:48:05       4 阅读
  4. Python语言-面向对象

    2024-07-10 20:48:05       7 阅读

热门阅读

  1. Redis 中的跳表(Skip List)

    2024-07-10 20:48:05       10 阅读
  2. 路由器是什么?

    2024-07-10 20:48:05       10 阅读
  3. redis实现延时队列

    2024-07-10 20:48:05       10 阅读
  4. Shell选择结构

    2024-07-10 20:48:05       13 阅读
  5. Poincaré图和SD2计算参考

    2024-07-10 20:48:05       10 阅读
  6. C#控件总结

    2024-07-10 20:48:05       9 阅读
  7. STM32(一):安装环境

    2024-07-10 20:48:05       10 阅读
  8. 数据中台真的适合你的企业吗?

    2024-07-10 20:48:05       9 阅读
  9. [AIGC] ClickHouse的表引擎介绍

    2024-07-10 20:48:05       13 阅读
  10. go 函数

    2024-07-10 20:48:05       11 阅读
  11. 玩转springboot之springboot项目监测

    2024-07-10 20:48:05       10 阅读