反射机制,热更新,插件化
-
编译阶段:
- 开发者使用
.java
文件编写Java源代码。 - 通过JDK中的
javac
编译器,.java
文件被编译成.class
字节码文件。这一步骤是必要的,因为Java虚拟机(JVM)无法直接理解Java源代码。
- 开发者使用
-
为什么生成字节码:
- 字节码是一种中间代码形式,它使得Java程序能够在任何安装有JVM的平台上运行,实现“一次编写,到处运行”的目标。字节码既不依赖于源代码的高级特性,也不依赖于特定硬件平台的机器指令。
-
虚拟机如何识别class文件:
- 在Android环境中,
.class
文件会进一步转换成.dex
(Dalvik Executable) 文件,并打包进APK文件中。 - 当应用安装并启动时,Android运行时(ART或其前身Dalvik)会加载APK文件,并通过类加载器解析
.dex
文件里包含的类信息。这些类信息被保存在一个特定的数据结构中,称为pathList
。 -
pathList 的作用
pathList
是Android运行时环境中DexClassLoader
或PathClassLoader
内部使用的一个数据结构。它主要用于管理和组织从APK文件中加载的.dex
文件。-
存储.dex文件:当APK被安装到设备上时,其中包含的
.dex
文件需要被运行时环境读取和执行。这些.dex
文件首先被提取出来,并加载到运行时环境中。 -
组织类信息:为了有效地查找和加载类定义,Android使用了名为
pathList
的数据结构来保存对这些已加载.dex
文件的引用。每个条目(通常称为“元素”)代表一个或多个.dex
文件,这些文件包含了应用程序所需执行的所有类定义。
-
- 在Android环境中,
-
类加载过程:
- 当执行到需要特定类(例如
Test test = new Test();
)时,类加载器首先检查该类是否已经被加载到内存中。 - 如果未加载,则通过双亲委托机制尝试找到并加载该类。双亲委托机制确保了从最顶层的类加载器开始向下查找,优先使用系统提供的类定义。
- 类被成功加载后,在内存中创建一个
Class
对象作为该类型实例化模版。所有基于该类型创建的实例都将根据这个模版来构建。
- 当执行到需要特定类(例如
-
已经加载过的情况:
- 如果请求的类已经存在于内存中(之前已经被某个类加载器加载),则直接返回对应的
Class
对象而无需重新加载。
- 如果请求的类已经存在于内存中(之前已经被某个类加载器加载),则直接返回对应的
总结
- Java程序从源代码到执行涉及编译、打包、部署和运行等多个阶段。
- 字节码是为了跨平台兼容性而设计的中间表示形式。
- Android使用
.dex
格式和APK来分发和执行应用程序。 - 类在第一次需要时被动态地加载到内存中,并且每个类型在内存里只有一个
Class
对象作为模版。 - 双亲委托机制保证了类按照预定顺序被搜索和加载,并且系统库优先于用户定义库被使用。
4.Class对象
- 每个被成功加载并初始化的类,在JVM内部都会有一个
java.lang.Class
类型的对象与之对应。这个Class
对象包含了该类所有相关信息,如类名、父类、接口、方法、字段等。 Class
对象是在加载类时由JVM自动创建的。它们作为访问运行时类型信息的入口,是Java反射机制的基础。- 你可以通过多种方式获取到某个特定类的
Class
对象实例,例如:- 使用
.class
语法:比如String.class
- 调用对象实例上的
.getClass()
方法 - 使用
Class.forName()
静态方法
- 使用
示例
假设我们有一个名为 ExampleClass.java
的简单Java类:
public class ExampleClass { static { System.out.println("ExampleClass initializing..."); } }
当我们使用以下代码请求JVM加载这个类时:
Class<?> cls = Class.forName("ExampleClass");
执行流程大致如下:
- JVM使用系统类加载器尝试找到并读取
ExampleClass.class
文件。 .class
文件被解析成运行时数据结构,并存储在方法区。- 进行链接操作,包括验证、准备和解析步骤。
- 初始化
ExampleClass
, 执行静态代码块打印输出信息。 - 创建代表
ExampleClass
的Class<?> cls
对象实例。
总结
通过上述过程,.class
文件最终被转换成了JVM可以理解和执行的格式,并且每个成功加载的类都会在JVM内部有一个相应的 Class<?> cls = Class.forName("ExampleClass");
.
使用 Class.forName("ExampleClass")
这种方式来加载类可以被视为一种手动加载类的过程。这个方法在运行时动态地加载指定名称的类,并在首次使用时触发该类的初始化(比如执行静态代码块)。这与Java程序在正常执行过程中由JVM自动加载所需类的机制相区别。
在Java中,当我们提到Class
这个词时,它可以指代两种不同的概念,这可能会造成一些混淆。以下是对这两种情况的解释:
1. 自定义类(User-defined Classes)
当你在.java
文件中定义一个类时,比如:
public class MyClass { // 类体 }
这里的MyClass
是一个自定义类。它是由开发者根据需要创建的,用于封装数据和行为。在Java程序中,自定义类可以被实例化(使用new
关键字创建对象),也可以包含属性(字段)、方法、构造函数等组件。
2. java.lang.Class
类
另一方面,java.lang.Class
是Java反射API的一部分,每个自定义类在JVM内部都有一个与之对应的Class
对象实例。这个对象不是开发者直接编写的代码产生的实例,而是由JVM在运行时动态创建的。它包含了与其关联的那个自定义类的元数据信息,比如类名、父类、实现的接口、拥有的方法和属性等。
你可以通过多种方式获取到某个特定类对应的Class
对象实例,例如:(在这三种方法中,只有 Class.forName()
可以视作在某种程度上“手动”控制类加载和初始化过程。其他两种方法主要是基于已经存在或者预先知道的情况下获取 Class
对象)
- 使用
.class
语法:Class<MyClass> cls = MyClass.class;
- 调用对象上的
.getClass()
方法:Class<?> cls = myObject.getClass();
- 使用静态方法
Class.forName()
:Class<?> cls = Class.forName("com.example.MyClass");
区别总结
- 自定义类:由开发者根据业务需求直接编写并用于程序逻辑中。
java.lang.Class
:JVM为每个加载到内存中的类型(包括自定义类、接口、数组等)动态创建并管理的一个特殊对象,主要用于反射操作。
简而言之,在Java文件里面定义的“Class”指你编写并用来构建应用程序逻辑和数据结构的代码;而当我们谈论到“一个类对应的 java.lang.Class
对象”时,则指代JVM为该类型创建和维护元数据信息所使用的内部机制。