当前位置: 首页 > article >正文

JVM 类加载机制2

  • 扩展类加载器(Extension ClassLoader),该类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现,负责将 <JRE_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,例如swing系列、内置的js引擎、xml解析器等以javax开头的扩展类库都是由扩展类加载器加载,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader),该类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此也被称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,比如:我们自己编写的自定义类或第三方jar包。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
public class ClassLoaderTest {
	public static void main(String[] args) {
		 // (启动类)系统类加载器:
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader); //sun.misc.Launcher$AppClassLoader@73d16e93
        
        //扩展类加载器:
        ClassLoader extendClassLoader = systemClassLoader.getParent();
        System.out.println(extendClassLoader); //sun.misc.Launcher$ExtClassLoader@15db9742
        
        // 引导类加载器:
        ClassLoader bootstrapClassLoader = extendClassLoader.getParent();
        System.out.println(bootstrapClassLoader); // null
        
        // 用户自定义的类默认用系统类加载器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader); // sun.misc.Launcher$AppClassLoader@73d16e93
	}
}
5.3.什么情况下需要自定义类加载器?

1.隔离加载类。在某些框架内进行中间件与应用的模块之间进行隔离,把类加载到不同的环境。

2.修改类加载方式。

3.扩展加载源。比如:从数据库、网络、电视机顶盒进行类加载。

4.防止源码泄漏。比如:编译时字节码进行加密,需要通过自定义类加载器对字节码进行解密还原。

6.双亲委派模型

应用程序是由三种类加载器互相配合,从而实现类加载,除此之外还可以加入自己定义的类加载器。

类加载器之间的层次关系,称为双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition)来实现,而不是继承关系(Inheritance)。

6.1.双亲委派工作机制

一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。

6.2双亲委派的作用
  1. 每个类只会加载一次,解决了各个类加载器加载基础类的统一问题(基础类库由上层的加载器进行加载);
  2. 防止恶意破坏的类加载,内存中不会出现多份同样的字节码的系统类,保证Java程序安全稳定运行。

例如:java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath中的 Object使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中使用的所有的 Object 都是由启动类加载器所加载的 Object。

6.3双亲委派的实现源码

以下是抽象类 java.lang.ClassLoader 的代码片段,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException,此时尝试自己去加载。

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}
6.4.SPI打破双亲委派

SPI(Service Provider Interface),是一种服务发现机制,它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。

如下图,SPI核心类定义在rt.jar中(例如:java.lang.Driver接口),所以java.lang.Driver接口本身是由启动类加载器加载,当调用java.lang.Driver接口的实现类时,启动类加载器是无法加载实现类的,这个时候就提供了线程上下文类加载器(Thread Context ClassLoader)加载实现类,ThreadContextClassLoader是可以通过java.lang.Thread#setContextClassLoader方法设置类加载器,这样就打破了双亲委派的类加载模式。

7.对象的创建过程

Step1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

Step2:分配内存

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。内存分配的查找方式有 “指针碰撞” 和 “空闲列表” 两种。

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"。

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

Step5:执行 init 构造方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java 程序的视角来看,对象创建才刚开始,<init> 构造方法还没有执行,目前所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 构造方法,把对象按照程序逻辑的意愿进行初始化,这样一个真正可用的对象才算完整创建出来。


http://www.kler.cn/news/321599.html

相关文章:

  • R语言 基础笔记 2
  • 【数据结构】算法的时间复杂度
  • Python OpenCV精讲系列 - 滤波器深入理解(十四)
  • 手机换新,怎么把旧iPhone手机数据传输至新iPhone16手机
  • Linux 进程控制
  • C++学习,# 和 ## 运算符
  • 程序bug的修复之道
  • Kafka技术详解[6]: 创建主题
  • css div多边框斜角边框
  • 配置virtualbox,在windows中与ubuntu共享文件夹
  • Halcon基础系列1-基础算子
  • uni-app canvas文本自动换行
  • 探索 Snowflake 与 Databend 的云原生数仓技术与应用实践 | Data Infra NO.21 回顾
  • 基于matlab语音滤波系统
  • 排序--希尔排序
  • C#入门教程
  • python 实现knn sklearn K近邻分类算法
  • 最新的iOS 18版本和Android 15版本系统分别升级了哪些功能?
  • [大语言模型] 情感认知在大型语言模型中的近期进展-2024-09-26
  • SLF4J报错log4j又报错
  • 新书推荐——《深度学习精粹与PyTorch实践》
  • 网络安全专业,在校大学生如何赚外快,实现财富自由?零基础入门到精通,收藏这一篇就够了
  • Linux下的基本指令/命令(二)
  • 面试小妙招:轻松绕过五大“坑”,展现真实自我
  • python中数据处理库,机器学习库以及自动化与爬虫
  • 设计模式-行为型模式-中介者模式
  • 【C#】DllImport的使用
  • 力扣19 删除链表的倒数第N个节点 Java版本
  • eBPF系列:开发流程
  • 【HarmonyOS】鸿蒙仿iOS线性渐变实现