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,如下源码
相关文章:
java以及android类加载机制
类加载机制 一、Java类加载机制 java中,每一个类或者接口,在编译后,都会生成一个.class文件。 类加载机制指的是将这些.class文件中的二进制数据读入到内存中并对数据进行校验,解析和初始化。最终,每一个类都会在方…...
【Go】四、rpc跨语言编程基础与rpc的调用基础原理
Go管理工具 早期 Go 语言不使用 go module 进行包管理,而是使用 go path 进行包管理,这种管理方式十分老旧,两者最显著的区别就是:Go Path 创建之后没有 go.mod 文件被创建出来,而 go module 模式会创建出一个 go.mod…...
Linux CentOS系统安装SQL Server并结合内网穿透实现公网访问本地数据
🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞👍收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默&…...
输入捕获模式测频率PWM输入模式(PWMI)测占空比
一、概念介绍 输出比较: 比较电路输入的CNT、CCR大小关系 ,在通道引脚输出高低电平 二、*频率知识、测量方法补充 * N/fc得到标准频率的时长,也就是待测频率的周期 测频法代码实现:修改对射式红外传感器计次(上升沿…...
解锁VIP会员漫画:用Python爬虫轻松实现高清漫画下载
嗨喽~大家好呀,这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 环境使用: Python 3.10 Pycharm 模块使用: requests >>> pip install requests 数据请求模块 parsel >>> pip install parsel 数据解析模块…...
备战蓝桥杯---动态规划(入门3之子串问题)
本专题再介绍几种经典的字串问题。 这是一个两个不重叠字串和的问题,我们只要去枚举分界点c即可,我们不妨让c作为右区间的左边界,然后求[1,c)上的单个字串和并用max数组维护。对于右边,我们只要反向求单个字串和然后选左边界为c的…...
JavaScript:隐式类型转换与显式类型转换
文章目录 隐式类型转换(Implicit Type Conversion)1、字符串与数字的转换2、非布尔值到布尔值的转换3、在相等性比较中的转换4、对象到基础类型的转换5、在算术运算符中的其他转换 显式类型转换(Explicit Type Conversion)1、Numb…...
【电路笔记】-LR串联电路
LR串联电路 文章目录 LR串联电路1、概述2、示例1所有线圈、电感器、扼流圈和变压器都会在其周围产生磁场,由电感与电阻串联组成,形成 LR 串联电路。 1、概述 在本节有关电感器的第一个文章中,我们简要介绍了电感器的时间常数,指出流过电感器的电流不会瞬时变化,而是会以恒…...
Ansible 自动化运维工具的使用
目录 Ansible的简介 ansible 环境安装部署 ansible 命令行模块 command 模块 shell 模块 cron 模块 user 模块 group 模块 copy 模块 file 模块 hostname 模块 ping 模块 yum 模块 service/systemd 模块 script 模块 mount 模块 archive 模块 unarchive 模…...
亚马逊、ozon、速卖通、Lazada等跨境平台为什么评论老是被删
对于卖家而言,最难的并不是销售量,最难的是让客户在购买后能够留下一个高质量的review,毕竟现在的市场,以listing的排名为基准,以review数量多少和质量的高低来评判店铺的好坏 几乎所有的卖家都会有索评的烦恼&#x…...
手把手带你在Linux上安装带GPU加速的opencv库(C++版本)
1.安装依赖 sudo apt-get install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt-get install python-dev python-numpy python3-dev python3-numpy sudo apt-get install libtbb2 libtbb-dev libjpeg-dev l…...
【Linux】软件包管理器 yum | vim编辑器
前言: 软件包管理器 yum和vim编辑器讲解 文章目录 软件包管理器 yum编辑器-vim四种模式普通模式批量化注释和批量化去注释末行模式临时文件 软件包管理器 yum yum(Yellowdog Updater, Modified)是一个在基于 RPM(管理软件包的格式和工具集合&…...
vue常见问题
文章目录 data为什么是一个函数,而不是一个对象?什么情况下可以使用对象?key的作用,为什么不能用Index?render函数,h函数,和template什么关系?vue 是怎么解析template的? template会…...
ArcgisForJS基础
文章目录 0.引言1.第一个ArcgisForJS应用程序1.1.安装部署ArcgisForJS1.2.实现ArcgisForJS应用程序 2.开发与调试工具2.1.集成开发环境2.2.调试工具2.3.Firebug 0.引言 ArcGIS API for JavaScript是一款由Esri公司开发的用于创建WebGIS应用的JavaScript库。它允许开发者通过调…...
白话微机:5.解释串行接口以及一些考研面试问题
一. 前言(回顾世界观) 很久很久以前,有这样一个世界,这个世界有着现实世界一样的元素:那里的人又有一个别的名字叫做“数据”,人有0有1;人们也有住房,这些住房在这个世界叫做“存储器…...
版本控制(Git)
Fork 本课程网站的仓库 将版本历史可视化并进行探索是谁最后修改了 README.md文件?(提示:使用 git log 命令并添加合适的参数)最后一次修改_config.yml 文件中 collections: 行时的提交信息是什么?(提示&am…...
USB-C音频转接器:实现边充电边听歌的新选择 | LDR6020P
随着科技浪潮的推进,Type-C接口已逐渐成为电子设备的主流选择,以其正反随意插、高速传输和强大功能等独特优势,在日常生活中占据越来越重要的地位。而Type-C音频转接器,作为连接Type-C接口与音频设备的桥梁,正引领着音…...
C/C++ 怎么把多个静态库给整合成一个静态库?
来源:https://www.wikitechy.com/tutorials/linux/how-to-merge-two-ar-static-libraries-into-one 使用 libtool (这也是可移植性最强的方式)(但这通常要求两个子库也是 libtool 制作的) libtool --modelink cc -static -o libaz.la libab…...
OBD部署OceanBase集群-配置文件方式
前一篇文章介绍了OBD白屏可视化方式部署OceanBase集群 ,其原理是把可视化设置生成为一个配置文件,然后使用OBD命令部署集群 本篇想使用命令行加配置文件方式,只部署OceanBase和ODProxy两个组件 服务器参数配置和 oceanbase-all-in-one-*.ta…...
Flink介绍
Flink 介绍 文章目录 Flink 介绍1. 简介1.1 背景1.2 用途 2. 核心概念2.1 流(Stream)2.2 转换(Transformation)2.3 窗口(Window)2.4 状态(State) 3. 编程模型3.1 编程模型介绍3.2 程…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...
【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...
