java以及android类加载机制

news2024/11/27 10:26:42

类加载机制

一、Java类加载机制

java中,每一个类或者接口,在编译后,都会生成一个.class文件。

类加载机制指的是将这些.class文件中的二进制数据读入到内存中并对数据进行校验,解析和初始化。最终,每一个类都会在方法去保存一份元数据,在堆中创建一个与之对应的Class对象。

类的生命周期,经历7个阶段,分别是加载,验证,准备,解析,初始化,使用,卸载

类加载过程包括加载,验证,准备,解析,初始化

类加载时机

类加载时机也就是.class文件什么时候被读取到虚拟机的内存中,并且达到可用的状态。

大多数情况下,都遵循什么时候初始化来进行加载。

初始化时机:

  • 使用new实例化对象时,读取或者设置一个类的静态字段或者方法时。
  • 反射调用时,例如Class.forName(“com.xxx.ClassName”)
  • 初始化一个类的子类,会首先初始化子类的父类
  • Java虚拟机启动时标明的启动类
  • JDK8之后,接口中存在default方法,这个接口的实现来初始化时,接口会在其之前进行初始化。

类的加载过程

在这里插入图片描述

类的加载过程分5个阶段,其中,验证,准备,解析可以归纳为”连接“

**注:**这五个阶段,并不是严格意义上的按顺序完成,在类加载过程中,这些阶段会互相混合,交叉运行,最终完成类的加载和初始化。

加载

加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成三件事情:

  • 通过类的全限定名去找到其对应的.class文件
  • 将这个.class文件内的二进制数据读取出来,转化成方法区的运行时数据结构
  • 在java堆中生成一个代表这个类的java.lang.Class对象,作为对方法去中这些数据的访问入口
验证

Class文件中的内容是字节码,这些内容可以由任何途径产出,验证阶段的目的是保证文件内容里边的字节流符合Java虚拟机规范,且这些内容信息运行后不会危害虚拟机的自身的安全。

验证阶段会完成以下校验:

文件格式校验:验证字节码是否符合Class文件格式的规范

**元数据验证:**对字节码描述的元数据信息进行语义分析,要符合java语言规范

字节码验证:对类的方法体进行校验,确保这些方法在运行时是合法的,符合逻辑的

符号引用验证:发生在解析阶段,符号引用转为直接引用的时候。

验证阶段是非常重要的,但不是必须的,对程序运行期没有影响,如果在保证引用的类经过验证的情况下可以考虑使用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备

准备阶段,类的静态字段信息会得到内存分配并且被设置为初始值

  • 1.内存分配仅包括static修饰过的变量,而不包含实例变量,实例变量得等到对象实例化的时候分配内存
  • 2.初始值指的是变量数据类型的默认值,而不是被在java代码中显式赋予的值,当字段被final修饰成常量时,这个初始值就是java代码中显式赋予的值。

例如:public static int value = 3
类变量 value 在准备阶段设置的初始值是 0,不是 3。把value赋值为3的 putstatic 指令是在程序编译后,存放于类构造器 () 方法中的,所以把 value 赋值为 3 的动作将在初始化阶段才会执行。
当使用 final 修饰后:public static final int value = 3
类变量 value 在准备阶段设置的初始值是 3,不是 0。

  • 3.在JDK取消永久代后,方法区变成了一个逻辑上的区域,这些类变量的内存实际上是分配在java堆中的。

解析

这个阶段,虚拟机会把这个Class文件中,常量池的符号引用转换为直接引用。主要解析的是类或者接口,字段,类方法,接口方法,方法类型,方法句柄等符号引用。符号引用转换为直接应用的过程就是当前加载的这个类和它所引用的类正式进行连接的过程。

什么是符号引用?
java代码在编译期间,是不知道最终引用的类型,具体指向内存中的哪个位置的,此时会用一个符号引用,来表示具体引用的目标是谁,Java虚拟机规范中明确定义了符号引用的形式,符合这个规范的前提下,符号引用可以是任意值,只要能通过这个值能定位到目标

什么是直接引用?
直接引用就是可以直接或者间接指向目标内存位置的指针或句柄
引用的类型,还未加载初始化怎么办?
当出现这种情况,会触发这个引用对应类型的加载和初始化

初始化

类加载的最后一步,初始化的过程就是执行类构造器<clinit>()方法的过程 当初始化完成之后,类中static修饰的变量会赋予程序员实际定义的值,同时类中如果存在static代码块,也会执行这个静态代码里边的代码

<clinit>()方法的作用是什么?
在准备阶段,已经对类中static修饰的变量赋予了初始值。<clinit>()方法的作用,就是给这些变量赋予程序员实际定义的值。同时类中如果存在static代码块,也会执行这个静态代码块里边的代码
<clinit>()方法是什么?
<clinit>()方法和<init>方法是不同的,它们一个是类构造器,一个是实例构造器,java虚拟机会保证子类<clinit>()方法在执行前,父类的<clinit>已经执行完毕。而<init>方法则需要显性的调用父类的构造器
<clinit>()方法由编译器自动生成,但不是必须生成的,只有这个类存在static修饰的变量,或者类中存在静态代码块的时候,才会自动生成<clinit>()方法

加载过程总结

当一个符合java虚拟机规范的字节流文件,经历加载,验证,准备,解析,初始化这些阶段相互协作完成之后,加载阶段读取到的Class字节流信息,会按虚拟机规定的格式,在方法区保存一份,然后会在java堆中,会创建一个java.lang.Class类的对象,这个对象描述了这个类所有信息,也提供了这个类在方法区的访问入口。

方法区中,使用同一加载器的情况下,每个类只会有一份Class字节流信息
Java堆中,使用同一加载器的情况下,每个类中只会有一份java.lang.Class类的对象

类加载器

类加载器就是在加载阶段,通过类的全限定名,获取该类字节流数据的动作。

三层类加载器介绍

  • 启动类加载器(Bootstrap Class Loader):负责加载<JAVA_HOME>\lib目录,或者呗-Xbootclasspath参数指定的路径,例如jre/lib/rt.jar里所有的class文件。由c++实现,不是ClassLoader子类
  • 拓展类加载器(Extension Class Loader):负责加载Java平台中扩展功能的一些jar包,包括<JAVA_HOME>\lib\ext目录中或者java.ext.dirs指定目录下的jar包,由java代码实现。
  • 应用程序类加载器(Application Class Loader):程序开发者开发的应用程序,有他加载,负责加载ClassPath路径下的所有jar包

双亲委派模型

任何一个类加载器在接到一个类的加载请求时,都会先让其父类进行加载,只有父类无法加载(或者没有父类)的情况下,才尝试加载

在这里插入图片描述

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 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);
                }
            }
            return c;
    }

ClassLoader类中的加载示例

双亲委派模型的好处

使用双亲委派模型,可以保证,每一个类只会有一个类加载器,例如java最基础的Object类,它存放在rt.jar中,这是Bootstrap的职责范围,当向上委派到Bootstrap时就会被加载,但是如果没有双亲委派模型,可以任由自定义类加载器加载的话,Java的核心api就会被随意篡改

二、Android中的ClassLoader

1、类加载器类型

Android跟java有很大的渊源,基于jvm的java应用是通过classLoader来加载应用中的class的,Android对jvm优化过,使用的是dalvik虚拟机,且class文件会被打包进一个dex文件中,底层虚拟机有所不同,那么它们的类加载器也会有区别。

Andorid中最主要的类加载器有4个

  • BootClassLoader: 加载Android Framework层的class字节码文件(类似java的BootStrapClassLoader)
  • PathClassLoader: 加载已经安装到系统中的APK的class字节码文件(类似java的App ClassLoader)
  • DexClassLoader:加载指定目录的class字节码文件(类似java中的Custom ClassLoader)

Android 中的类加载器和java类加载器一样使用的是双亲委派模型

2、PathClassLoader与DexClassLoader的区别

1)使用场景

  • PathClassLoader: 只能加载已经安装到Android系统的apk文件(data/app目录),是Android默认使用的类加载器
  • DexClassLoader:可以加载任意目录下的dex/jr/zip文件,比PathClassLoader更灵活

2)代码差异

// PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

// DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

  • PathClassLoader与DexClassLoader都继承于BaseDexClassLoader
  • PathClassLoader与DexClassLoader在构造函数中都调用了父类的构造函数,但DexClaccLoader多传了一个optimizeDirectory

3、BaseDexClassLoader

1) 构造函数
public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    ...
    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent){
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
    ...
}

  • dexPath:要加载的程序文件(一般是dex文件,也可以是jar/apk/zip文件)所在目录
  • optimizedDirectory:dex文件的输出目录(因为在加载jar/apk/zip等压缩格式的文件时会解压出其中的dex文件,该目录就是专门用于存放这些被解压出来的dex文件)
  • liraryPath:加载程序文件时需要用到的库路径
  • parent:父加载器

pathClassLoader只会加载已安装包中的dex文件,而dexClassLoader不仅仅可以加载dex文件还可以加载jar,apk,zip中的dex。jar apk zip就是一些压缩格式,要拿到压缩包里边的dex文件就需要解压。所以,DexClassLoader在调用父构造函数时会指定一个解压目录

2) findClass()

类加载器会提供一个方法来供外界找到它所加载的class,该方法就是findClass()。

private final DexPathList pathList;

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    // 实质是通过pathList的对象findClass()方法来获取class
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}

可以看到,BaseDexClassLoader的findClass()方法实际上是通过DexPathList对象的findClass()方法来获取class,而这个DexPathLIst对象恰好在之前的BaseDexClassLoader构造函数中就已经被创建好了。

4.DexPathList

1) 构造函数
private final Element[] dexElements;

public DexPathList(ClassLoader definingContext, String dexPath,
        String libraryPath, File optimizedDirectory) {
    ...
    this.definingContext = definingContext;
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
    ...
}

构造函数中,保存了当前类加载器definingContext,并调用的makeDexElements()得到Element集合

通过对splitDexPath(dexPath)的源码追溯,发现该方法的作用其实就是将dexPath目录下的所有程序文件转变成一个File集合。而且还发现,dexPath是一个用冒号(“:”)作为分隔符把多个程序文件目录拼接起来的字符串(如:/data/dexdir1:/data/dexdir2:…)。

makeDexElements()方法

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) {
    // 1.创建Element集合
    ArrayList<Element> elements = new ArrayList<Element>();
    // 2.遍历所有dex文件(也可能是jar、apk或zip文件)
    for (File file : files) {
        ZipFile zip = null;
        DexFile dex = null;
        String name = file.getName();
        ...
        // 如果是dex文件
        if (name.endsWith(DEX_SUFFIX)) {
            dex = loadDexFile(file, optimizedDirectory);

        // 如果是apk、jar、zip文件(这部分在不同的Android版本中,处理方式有细微差别)
        } else {
            zip = file;
            dex = loadDexFile(file, optimizedDirectory);
        }
        ...
        // 3.将dex文件或压缩文件包装成Element对象,并添加到Element集合中
        if ((zip != null) || (dex != null)) {
            elements.add(new Element(file, false, zip, dex));
        }
    }
    // 4.将Element集合转成Element数组返回
    return elements.toArray(new Element[elements.size()]);
}

总体来说,DexPathList的构造函数是将一个个的程序文件(可能是dex、apk、jar、zip)封装成一个个Element对象,最后添加到Element集合中。

Android的类加载器(不管是PathClassLoader,还是DexClassLoader),它们最后只认dex文件,而loadDexFile()是加载dex文件的核心方法,可以从jar、apk、zip中提取出dex,

2) findClass()

DexPathList的findClass()方法

public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        // 遍历出一个dex文件
        DexFile dex = element.dexFile;

        if (dex != null) {
            // 在dex文件中查找类名与name相同的类
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

DexPathList的findClass()方法很简单,就只是对Element数组进行遍历,一旦找到类名与name相同的类时,就直接返回这个class,找不到则返回null。

调用DexFile的loadClassBinaryName()方法来加载class?这是因为一个Element对象对应一个dex文件,而一个dex文件则包含多个class。也就是说Element数组中存放的是一个个的dex文件,而不是class文件。这可以从Element这个类的源码和dex文件的内部结构看出。

android类加载器与java类加载器异同

根据前面的分析,我们总结下,android与java在类加载上的异同

相同:

  • Android类加载器和Java的类加载器工作机制是类似的,使用双亲委托机制

不同:

  • 加载的字节码不同 Android虚拟机运行的是dex字节码,Java虚拟机运行的class字节码。
  • 类加载器不同以及类加载器的类体系结构不同 如上面的类加载器结构图
  • BootClassLoader和Java的BootStrapClassLoader区别:Android虚拟机中BootClassLoader是ClassLoader内部类,由java代码实现而不是c++实现,是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见,所以我们没法使用。 Java虚拟机中BootStrapClassLoader是由原生代码(C++)编写的,负责加载java核心类库(例如rt.jar等)
  • ClassLoader类中的findBootstrapClassOrNull方法,android sdk直接返回null,jdk会去调用native方法findBootstrapClass,如下源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1456480.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈TCP协议的可靠含义和三次握手

TCP协议全称传输控制协议&#xff08;Transmission Cotrol Protocol&#xff09; 1、TCP协议运行在哪一层 TCP运行在运输层。 2、TCP协议的可靠是什么意思 步入主题&#xff0c;很多同学的回答是TCP三次握手确保了可靠连接&#xff0c;这样说非常不严谨&#…

openai公司的sora目前仅限于内部人士使用,并未开启对外接口

openai公司的sora目前仅限于内部人士使用,并未开启对外接口&#xff01;既然大家都了解到了sora具备相当强大的视频创作能力&#xff0c;肯定都想尝试一下了。但是&#xff0c;经过笔者的申请发现&#xff0c;目前openai官方并未对外开放sora的调用接口。如下图所示&#xff1a…

梵宁教育陪你击退“假期综合征”

转眼间龙年春节小长假已然结束&#xff0c;学生党寒假余额也严重不足&#xff0c;光是想到复工、复学&#xff0c;是不是已经感到心情沉重、疲惫、乏力、头晕&#xff0c;好像身体被掏空&#xff1f;根据小编的经验&#xff0c;你可能中招了假期综合征。快来测测你有以下情况吗…

Eclipse - Formatter

Eclipse - Formatter References Window -> Preferences -> C/C -> Code Style -> Formatter BSD/Allman [built-in] or K& R [built-in] References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

工作中的得力助手

在这个快节奏的工作环境中&#xff0c;提高工作效率已经成为了每个人的当务之急。而工作实用软件无疑是成就这一目标的得力助手。在这海量软件中&#xff0c;我将为您推荐五款我个人认为最值得一试的软件&#xff0c;它们将为您提供全方位的工作支持&#xff0c;助您事半功倍。…

iPhone 16 组件泄露 揭示了新的相机设计

iPhone 16 的发布似乎已经等了很长一段时间&#xff0c;但下一个苹果旗舰系列可能会在短短 7 个月内与我们见面——而新的组件泄漏让我们对可能即将到来的重新设计有了一些了解。后置摄像头模块。 爆料者 Majin Bu&#xff08;来自 MacRumors&#xff09;获得的示意图显示&…

【Linux】进程的初步认识(二)

进程的初步认识 前言查看进程通过系统调用创建进程关于创建进程的几点补充 前言 之前的一篇文章(文章链接)已经初步对于进程有了一个认识&#xff0c;这篇文章主要是介绍如何去查看进程的相关信息以及创建一个进程的相关知识 查看进程 查看进程的信息可以在/proc系统文件夹中查…

【开源】SpringBoot框架开发服装店库存管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 服装档案模块2.4 服装入库模块2.5 服装出库模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 角色表3.2.2 服装档案表3.2.3 服装入库表3.2.4 服装出库表 四、系统展示五、核心代码5.…

借助Aspose.BarCode条码控件,C# 中的文本转 QR 码生成器

二维码用于在较小的空间内存储大量数据。它们易于使用&#xff0c;可以通过智能手机或其他设备扫描来打开网站、观看视频或访问其他编码信息。在这篇博文中&#xff0c;我们将学习如何使用 C# 以编程方式生成基于文本的 QR 码。我们将提供分步指南和代码片段&#xff0c;帮助您…

文件且目录损坏无法读取怎么办?

文件及目录损坏无法读取是计算机使用过程中的常见故障&#xff0c;这可能是由于多种原因导致的&#xff0c;例如硬件故障、文件系统错误、病毒感染或不当操作等。本文将对这一问题进行深入分析&#xff0c;探讨其根本原因&#xff0c;并提供相应的解决方法&#xff0c;包括数据…

行业扩展 | 什么是设备管理系统?你了解多少……

▲ 设备管理是以工厂设备为管理对象,应用科学的理论、方法,通过一系列的技术,经济,组织等措施对各种生产设备和工具的有效管理和维护,以确保生产线的正常运行和提高生产效率的现代化管理。 设备管理组织架构 在企业生产运营中,设备管理工作效率的高低往往取决于其管理架构的好…

堆详解以及简单的堆排序(源代码)

一、什么是堆&#xff1f; 堆是将数组看作一颗完全二叉树 大堆&#xff1a;任意一个父亲大于等于孩子 小堆&#xff1a;任意一个父亲小于等于孩子 有序数组一定是堆 但是堆不一定有序 注意&#xff1a;此“堆”是一个数据结构&#xff0c;用来表示完全二叉树 还有另外一个“…

渗透某巨型企业某个系统的奇葩姿势

本文由掌控安全学院 - urfyyyy 投稿 前言 这个月都在做一个巨型合作企业的渗透测试&#xff0c;这个系统本无方式getshell&#xff0c;得亏我心够细&#xff0c;想的多&#xff0c;姿势够骚。 文中重码&#xff0c;且漏洞已修复。 过程 找到getshell点 目标系统功能很少&…

Python实现时间序列分析霍尔特季节性平滑模型(Holt算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 霍尔特季节性平滑模型是指数平滑技术的一种扩展形式&#xff0c;由E. S. Holt和P. R. Winters分别独立…

nginx-在应用程序中发现不必要的Http响应头

描述:一般是在返回的响应表头中出现了Server键值对 解决方案: 通过headers-more-nginx-module模块将Server去除 https://github.com/openresty/headers-more-nginx-module ./configure --add-module/usr/local/headers-more-nginx-modulemakemake install 编译完成后&#…

Maven高级(一)

文章目录 Maven高级&#xff08;一&#xff09;1. 分模块设计与开发1.1 介绍1.2 实践1.2.1 分析1.2.2 实现 1.3 总结 2. 继承与聚合2.1 继承2.1.1 继承关系2.1.1.1 思路分析2.1.1.2 实现 2.1.2 版本锁定2.1.2.1 场景2.1.2.2 介绍2.1.2.3 实现2.1.2.4 属性配置 2.2 聚合2.2.1 介…

window10 远程桌面

1、设置需要被远程的电脑设置不走 设置电脑名字 开始–>设置–>系统–>关于–>系统设置–>重命名计算机名称 设置远程&#xff0c;这一步很重要。你可以选择指定用户来进行远程桌面。通过“高级–》立即查找”可以找到自己想要设置的用户&#xff0c;此处“名…

ERP定制+跨境电商:双剑合璧,打造全球商业帝国

在当今全球化的商业环境下&#xff0c;跨境电商已成为许多企业拓展市场、实现增长的重要途径。然而&#xff0c;要成功经营跨境电商业务&#xff0c;企业需要面对诸多挑战&#xff0c;如跨国物流、支付结算、客户服务等方面的复杂问题。而ERP定制则能为跨境电商提供强大的支持&…

碳化硅模块使用烧结银双面散热DSC封装的优势与实现方法

碳化硅模块使用烧结银双面散热DSC封装的优势与实现方法 新能源车的大多数最先进 (SOTA) 电动汽车的牵引逆变器体积功率密度范围从基于 SSC-IGBT 的逆变器的 <10 kW/L 到基于 SSC-SiC 的逆变器的约 25 kW/L。100 kW/L 代表了这一关键指标的巨大飞跃。 当然&#xff0c;随着新…

每日汇评:黄金有望在复苏之路上重新夺回关键的2025美元关口

周一&#xff0c;金价在本周初延续了其复苏模式&#xff1b; 随着投资者重新评估美联储降息押注&#xff0c;美元跟随美债收益率走软&#xff1b; 黄金买家需要突破21日移动均线2025美元,RSI指数稳定在50以下&#xff1b; 随着买家将上周的复苏模式延续到周一&#xff0c;黄金价…