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 loadedClass<?> 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()方法来获取classClass 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.rhkb.cn/news/259931.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入实战:ElasticSearch的Rest API与迭代器模式在高效查询中的应用

在我们公司&#xff0c;大多数Java开发工程师在项目中都有使用Elasticsearch的经验。通常&#xff0c;他们会通过引入第三方工具包或使用Elasticsearch Client等方式来进行数据查询。然而&#xff0c;当涉及到基于Elasticsearch Rest API的/_sql?formatjson接口时&#xff0c;…

CentOS上如何配置手动和定时任务自动进行时间同步

场景 Linux(Centos)上使用crontab实现定时任务(定时执行脚本)&#xff1a; Linux(Centos)上使用crontab实现定时任务(定时执行脚本)_centos 定时任务-CSDN博客 Winserver上如何配置和开启NTP客户端进行时间同步&#xff1a; Winserver上如何配置和开启NTP客户端进行时间同步…

motplotlib图例案例1:通过多个legend完全控制图例显示顺序(指定按行排序 or 按列排序)

这个方法的核心&#xff0c;是手动的获得图中的handlers和labels&#xff0c;然后对它们进行切分和提取&#xff0c;最后分为几个legend进行显示。代码如下&#xff1a; 后来对下面的代码进行修改&#xff0c;通过handlers, labels get_legend_handles_labels(axs[axis])自动的…

Unity所有关于旋转的方法详解

前言&#xff1a;欧拉角和四元数的简单描述 我们在Inspector面板上看到的rotation其实是欧拉角&#xff0c; 我们将Inspector面板设置成Debug模式&#xff0c;此时看到的local Rotation才是四元数。 Unity中的欧拉旋转是按照Z-X-Y顺规执行的旋转&#xff0c;一组欧拉旋转过程中…

Transformer面试十问

1 Scaled Dot-Product Attention中为什么要除以 d k \sqrt{d_k} dk​ ​? 1. 从纯数学上考虑&#xff1a;对于输入均值为0,方差为1的分布&#xff0c;点乘后结果其方差为dk&#xff0c;所以需要缩放一下。下图为原论文注释。 2. 从神经网络上考虑&#xff1a;防止在计算点积…

杨氏矩阵和杨辉三角

杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 分析 若要满足要求时间复杂度小于O(N)&#xff0c;就不能每一行一个个…

树和二叉树的基本知识

一、树的概念及结构 1.树的概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 。 有一个 特殊…

阿里云服务器“镜像”全方面解析

阿里云服务器镜像怎么选择&#xff1f;云服务器操作系统镜像分为Linux和Windows两大类&#xff0c;Linux可以选择Alibaba Cloud Linux&#xff0c;Windows可以选择Windows Server 2022数据中心版64位中文版&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说下阿里云服务器操…

软考29-上午题-排序

一、排序的基本概念 1-1、稳定性 稳定性指的是相同的数据所在的位置经过排序后是否发生变化。若是排序后&#xff0c;次序不变&#xff0c;则是稳定的。 1-2、归位 每一趟排序能确定一个元素的最终位置。 1-3、内部排序 排序记录全部存放在内存中进行排序的过程。 1-4、外部…

【网络安全 | 网络协议】一文讲清HTTP协议

HTTP概念简述 HTTP&#xff08;Hypertext Transfer Protocol&#xff09;协议&#xff0c;又称超文本传输协议&#xff0c;用于传输文本、图像、音频、视频以及其他多媒体文件。它是Web应用程序通信的基础&#xff0c;通过HTTP协议&#xff0c;Web浏览器可以向Web服务器发起请…

电路设计(20)——数字电子钟的multism仿真

1.设计要求 使用数字芯片&#xff0c;设计一个电子钟&#xff0c;用数码管显示&#xff0c;可以显示星期&#xff0c;时、分、秒&#xff0c;可以有按键校准时间。有整点报警功能。 2.设计电路 设计好的multism电路图如下所示 3.芯片介绍 时基脉冲使用555芯片产生。在仿真里面…

挑战杯 地铁大数据客流分析系统 设计与实现

文章目录 1 前言1.1 实现目的 2 数据集2.2 数据集概况2.3 数据字段 3 实现效果3.1 地铁数据整体概况3.2 平均指标3.3 地铁2018年9月开通运营的线路3.4 客流量相关统计3.4.1 线路客流量排行3.4.2 站点客流量排行3.4.3 入站客流排行3.4.4 整体客流随时间变化趋势3.4.5 不同线路客…

NodeLocal DNS介绍及部署应用

1 NodeLocal DNS是什么&#xff1f; NodeLocal DNSCache 通过在集群节点上运行一个 DaemonSet 来提高 clusterDNS 性能和可靠性。处于 ClusterFirst 的 DNS 模式下的 Pod 可以连接到 kube-dns 的 serviceIP 进行 DNS 查询。通过 kube-proxy 组件添加的 iptables 规则将其转换为…

蓝桥杯DP算法——背包问题(C++)

目录 一、01背包问题 二、完全背包问题 三、多重背包问题 四、多重背包问题&#xff08;优化版&#xff09; 五、分组背包问题 一、01背包问题 01背包问题就是有N件物品&#xff0c;一个空间大小为V的背包&#xff0c;每个物品只能使用一次&#xff0c;使得背包中所装物品…

2019年江苏省职教高考计算机技能考试——一道程序改错题的分析

题目&#xff1a;函数将str字符串中的5个数字字符串转换为整数&#xff0c;并保存在二维数组m的最后一行&#xff0c;各元素为3、-4、16、18、6。并经函数move处理后&#xff0c;运行结果如下&#xff1a; 18 6 3 -4 16 16 18 6 3 -4 -4 16 …

【AIGC】Stable Diffusion之模型微调工具

推荐一款好用的模型微调工具&#xff0c;cybertron furnace 是一个lora训练整合包&#xff0c;提供训练 lora 模型的工具集或环境。集成环境包括必要的依赖项和配置文件、预训练脚本&#xff0c;支持人物、二次元、画风、自定义lora的训练&#xff0c;以简化用户训练 lora 模型…

【Linux】软件包管理器 yum | vim编辑器

前言: 软件包管理器 yum和vim编辑器讲解 文章目录 软件包管理器 yum编辑器-vim四种模式普通模式批量化注释和批量化去注释末行模式临时文件 软件包管理器 yum yum&#xff08;Yellowdog Updater, Modified&#xff09;是一个在基于 RPM&#xff08;管理软件包的格式和工具集合&…

软考学习--计算机组成原理与体系结构

计算机组成原理与体系结构 数据的表示 进制转换 R 进制转换为 10 进制–按权展开法 10进制转换为2进制 原码 反码 补码 移码 原码 &#xff1a;数字的二进制表示反码 &#xff1a; 正数的反码等于原码&#xff0c;负数的反码等于原码取反补码&#xff1a; 正数的补码等…

跟着pink老师前端入门教程-day24

四、移动端WEB开发之响应式布局 1、响应式开发 1.1 响应式开发原理 就是使用媒体查询针对不同宽度的设备进行布局和样式的设置&#xff0c;从而适配不同设备的目的。 1.2 响应式布局容器 响应式需要一个父级做为布局容器&#xff0c;来配合子级元素来实现变化效果。 原理…

世界顶级名校计算机专业,都在用哪些书当教材?(文末送书)

目录 01《深入理解计算机系统》02《算法导论》03《计算机程序的构造和解释》04《数据库系统概念》05《计算机组成与设计&#xff1a;硬件/软件接口》06《离散数学及其应用》07《组合数学》08《斯坦福算法博弈论二十讲》参与规则 清华、北大、MIT、CMU、斯坦福的学霸们在新学期里…