java以及android类加载机制

类加载机制

一、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.mfbz.cn/a/394987.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【 JS 进阶 】原型对象、面向对象

目标 了解构造函数原型对象的语法特征&#xff0c;掌握 JavaScript 中面向对象编程的实现方式&#xff0c;基于面向对象编程思想实现 DOM 操作的封装。 了解面向对象编程的一般特征掌握基于构造函数原型对象的逻辑封装掌握基于原型对象实现的继承理解何为原型链及其作用能够处理…

文物保护系统守护历史岁月,成都青铜展科技闪耀

一、“吉金万里-中国西南青铜文明展”隆重开幕 1月27日&#xff0c;“吉金万里-中国西南青铜文明展”在成都金沙遗址博物馆向公众开放&#xff0c;奉上一场精彩的青铜文明“盛宴”。本次展览汇集了中国西南地区32家文博单位&#xff0c;以青铜器为代表的294件经典文物&#xf…

人工智能_普通服务器CPU_安装清华开源人工智能AI大模型ChatGlm-6B_001---人工智能工作笔记0096

使用centos安装,注意安装之前,保证系统可以联网,然后执行yum update 先去更新一下系统,可以省掉很多麻烦 20240219_150031 这里我们使用centos系统吧,使用习惯了. ChatGlm首先需要一台个人计算机,或者服务器, 要的算力,训练最多,微调次之,推理需要算力最少 其实很多都支持C…

腾讯云助力酒店IT系统上云,实现出海业务的双重优势

潮起潮涌&#xff0c;随着时代浪潮的翻涌&#xff0c;生活处处可见是巨大的变化&#xff0c;衣食住行都有了更多更大的需求&#xff0c;出门旅游观赏当地风景品尝特色美食的前提是要住好&#xff0c;只有休息好了才有更多的精力去游玩。酒店系统的升级上云让登记变得更加便捷&a…

C# 12 中新增的八大功能你都知道吗?

一、主构造函数 在 Visual Studio 2022 版本 17.6 预览版 2 中引入。 从 C# 12 开始&#xff0c;可以在类和结构中声明主构造函数。主构造函数参数都在类的整个主体的范围内。为了确保显式分配所有主构造函数参数&#xff0c;所有显式声明的构造函数都必须使用 this() 语法调用…

基于SpringBoot的药品管理系统

基于SpringBoot的药品管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 药品详情 个人中心 员工界面 管理员界面 摘要 随着医疗技术的不断发展和人们健…

电子元器件基础7---集成电路

二极管三极管再往上就是四极管、五极管么?不,四极管还有但是我没用过。再往上我们需要学习各种阻容二极管和三极管的组合,也就是今天要介绍的集成电路,它的集成度从几个晶体管组合的元器件到上亿个晶体管组成的CPU,器件数量越多集成度越高同时其功能也更加复杂。 在这里我…

【Java EE初阶十四】网络编程TCP/IP协议(一)

1. 网络编程 通过网络&#xff0c;让两个主机之间能够进行通信->就这样的通信来完成一定的功能&#xff0c;进行网络编程的时候&#xff0c;需要操作系统给咱们提供一组API&#xff0c;通过这些API来完成编程&#xff1b;API可以认为是应用层和传输层之间交互的路径&#xf…

波奇学Linux:动态库加载和进程通信

进程加载的地址 逻辑地址加载到物理内存时&#xff0c;每一条指令有自己的地址。此时指令有逻辑地址和物理地址 如何执行第一条指令&#xff1f; 代码有entry入口地址&#xff0c;cpu的eip寄存器保存入口地址&#xff0c;进程获得入口地址后运行&#xff0c;同时页表建立物理…

【ArcGIS Pro二次开发】(81):玩个花活_控规指标块生成

一、要实现的效果 废话不多说&#xff0c;这次要实现的是类似控规指标块的标注&#xff1a; 这里只是示例&#xff0c;用了5个格子&#xff0c;做成9个格子也是可以的。 在实现这个效果最关键的是要用到Pro中的复合标注。 关于复合标注的用法可以搜一下帮助里的【使用复合注…

【Java EE初阶十九】网络原理(四)

4. 数据链路层 数据链路层也有很多种协议&#xff0c;其中一个比较常见常用的,就是“以太网协议”&#xff08;通过网线/光纤, 来通信所使用的协议叫做以太网协议&#xff0c;以太网是横跨数据链路层 物理层&#xff09;&#xff1b; 4.1 以太网数据帧格式 帧头 载荷(IP 数据…

全视通智慧社区居家养老解决方案如何让老人住的更舒心?

随着老龄化社会的加速发展&#xff0c;老年人的养老问题愈发受到社会关注。传统的养老方式已经无法满足现代老年人的需求&#xff0c;他们更加注重生活的品质和舒适度。为了响应《城市居家适老化改造指导手册》要求&#xff0c;全视通智慧社区&居家养老解决方案应运而生&am…

ChatGPT对软件测试的影响!

ChatGPT 是一个经过预训练的 AI 语言模型&#xff0c;可以通过聊天的方式回答问题&#xff0c;或者与人闲聊。它能处理的是文本类的信息&#xff0c;输出也只能是文字。它从我们输入的信息中获取上下文&#xff0c;结合它被训练的大模型&#xff0c;进行分析总结&#xff0c;给…

Linux系统——防火墙拓展及重点理解

目录 一、iptables 1.基本语法 2.四表五链——重点记忆 2.1四表 2.2五链 2.3总结 3.iptables选项示例 3.1 -Z 清空流量计数 3.2 -P 修改默认规则 3.3 -D 删除规则 3.4 -R 指定编号替换规则 5.白名单 6.通用匹配 7.示例 7.1添加回环网卡 7.2可以访问端口 7.3 主…

软件技术实训解决方案2024

软件技术实训解决方案 软件产业在新兴产业中占据重要地位&#xff0c;是推动数字经济发展的关键力量。面对“十四五”时期的新机遇和挑战&#xff0c;我国已经明确了加强关键数字技术创新应用的战略方向&#xff0c;并将软件和信息技术服务业的发展列为重点。这不仅是为了加快…

信息密码管理

密码管理 密码技术时保护信息安全的最有效手段,也是保护信息安全按按的最关键技术。各国政府相应出台了各种密码管理政策用于控制密码技术、监控密码市场等。目前我国密码管理相关的机构是国家密码管理局,全称国家商用密码管理办公室。 国家出台密码相关的主要政策有《商用…

利用R语言绘制相关性热图

数据示例&#xff08;归一化处理后&#xff09;&#xff1a; install.packages("corrplot") install.packages("ggplot2") install.packages("ggpubr") library(corrplot) install.packages("GGally") library(GGally) library(ggplot…

【BIOS】解锁BIOS隐藏菜单/高级选项

免责声明&#xff1a;修改BIOS存在风险&#xff0c;请谨慎操作。作者不对因操作不当而导致的任何后果负责。 一些默认的BIOS菜单选项相当有限&#xff0c;无法进行一些高级选项的修改。通过修改BIOS内容&#xff0c;我们可以解锁被隐藏的高级菜单&#xff0c;如Above 4G Decod…

Windows11(非WSL)安装Installing llama-cpp-python with GPU Support

直接安装&#xff0c;只支持CPU。想支持GPU&#xff0c;麻烦一些。 1. 安装CUDA Toolkit (NVIDIA CUDA Toolkit (available at https://developer.nvidia.com/cuda-downloads) 2. 安装如下物件&#xff1a; gitpythoncmakeVisual Studio Community (make sure you install t…

《计算思维导论》笔记:10.5 应用关系运算进行数据库的查询

《大学计算机—计算思维导论》&#xff08;战德臣 哈尔滨工业大学&#xff09; 《10.5 应用关系运算进行数据库的查询》 一、引言 我们接着给大家介绍数据库中的关系运算&#xff1a;应用关系运算&#xff0c;进行数据库的查询。 二、利用关系运算进行查询 我们前面给大家介绍…
最新文章