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.hqwc.cn/news/474980.html

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

相关文章

4核8G云服务器够用吗?支持多少人?

4核8G服务器支持多少人同时在线访问&#xff1f;阿腾云的4核8G服务器可以支持20个访客同时访问&#xff0c;关于4核8G服务器承载量并发数qps计算测评&#xff0c;云服务器上运行程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&…

VMWare ubuntu共享宿主机window11文件夹

宿主机window的设置 找到需要共享的文件夹&#xff0c;比如我需要share文件夹共享到虚拟机中 点击“共享”文件夹属性&#xff0c;如果找不到“共享”选项卡&#xff0c;需要在下面的“选项”中 注意勾选“使用共享向导&#xff08;推荐&#xff09;”&#xff0c;如果已经勾…

RabbitMQ五大常用工作模式

1.简单队列 消息生产者 public class Send {private static final String QUEUE_NAME "hello";public static void main(String[] args) throws Exception {// 连接工厂ConnectionFactory factory new ConnectionFactory();factory.setHost("192.168.101.128&…

谷达冠楠电商:现在开网店能赚钱吗

随着互联网技术的迅猛发展&#xff0c;电子商务已成为现代商业的重要组成部分。许多人纷纷涉足网店经营&#xff0c;希望通过线上渠道实现创业梦想。然而&#xff0c;“现在开网店能赚钱吗?”这个问题的答案并不是绝对的&#xff0c;而是取决于多种因素。 网络市场的低门槛和广…

软考高项总结:第8章整合管理

一、管理基础 1、项目整合管理由项目经理负责,项目经理负责整合所有其他知识领域的成果,并掌握项目总体情况。项目整合管理的责任不能被授权或转移,项目经理必须对整个项目承担最终责任。整合是项目经理的一项关键技能。执行项目整合时项目经理承担双重角色: (1)组织层…

C++智能指针的知识!

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 大家好呀&#xff0c;我是PingdiGuo_guo&#xff0c;今天我们来学习一下智能指针。 文章目录 1.智能指针的概念 2.智能指针的思想 3.智能指针的作用 3.1 自动内存管理 3.2 共享所有权 3.3 避免悬挂指针…

【开源】SpringBoot框架开发校园疫情防控管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生2.2 老师2.3 学校管理部门 三、系统展示四、核心代码4.1 新增健康情况上报4.2 查询健康咨询4.3 新增离返校申请4.4 查询防疫物资4.5 查询防控宣传数据 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBoot…

React 更改程序入口点(index.js文件位置变更)

食用前提示&#xff1a;本文基于已经快速配置好的React环境而作&#xff0c;配置React环境详见拙作&#xff1a;React环境配置-CSDN博客~ 一、了解默认入口点 使用create-react-app快速搭建react环境后&#xff0c;npm start启动程序的默认入口点为/src/index(即src目录下的ind…

ChatGPT的大致原理

国外有个博主写了一篇博文&#xff0c;名字叫TChatGPT: Explained to KidsQ」&#xff0c; 直译过来就是&#xff0c;给小孩子解释什么是ChatGPT。 因为现实是很多的小孩子已经可以用父母的手机版ChatGPT玩了 &#xff0c;ChatGPT几乎可以算得上无所不知&#xff0c;起码给小孩…

Linux——信号(1)

在我们使用Linux系统的时候我们经常会使用ctrl c的方式来终止进程&#xff0c;也 会使用kill命令来杀掉进程&#xff0c;评判进程退出的健康程度中也有信号的身影。那 么Linux中的信号到底是什么&#xff1f;今天就由我来介绍Linux中的信号。1. 信号的概念 要了解计算机中的信…

一.重新回炉spring框架: 理解Spring IoC

1. 写在前面的话 说实话&#xff0c;重试java开发工作时间也不短了&#xff0c;对于spring框架&#xff0c;也是天天用&#xff0c;这期间也碰到了很多问题&#xff0c;也解决了很多问题。可是&#xff0c;总感觉对spring还是一知半解&#xff0c;不能有个更加全面的理解。既然…

C++初阶:容器适配器介绍、stack和queue常用接口详解及模拟实现

介绍完了list类的相关内容后&#xff1a;C初阶&#xff1a;适合新手的手撕list&#xff08;模拟实现list&#xff09; 接下来进入新的篇章&#xff0c;stack和queue的介绍以及模拟&#xff1a; 文章目录 1.stack的初步介绍2.stack的使用3.queue的初步介绍4.queue的使用5.容器适…