一个Java从编码完成到最终执行文件实际上是一个非常简单的步骤,编译和操作,今天来聊一聊java类加载。不知怎么的,也许是我多愁善感。我突然觉得这似乎和我们的生活很相似。从小学到高中,我们不断学习,准备高考,然后得到我们想要的操作结果—喜欢的大学通知书。(尽量把关键词往前提哈)
我们所说的类加载过程即是指JVM虚拟机把.将class文件中的类似信息加载到内存中,并分析生成相应的class对象的过程。首先是开发人员编写的 Java 源代码(.java文件编译成 Java 字节码(.class文件),然后类加载器将读取此文件 .class 并将文件转换成 java.lang.Class 的实例。有了该 Class 实例后,Java 可使用虚拟机 newInstance 这种方法创造了它的真实对象。
在java出生时,我喊了一个激动人心的口号:“Write Once, Run Anywhere”,为了实现这一目的,Sun 许多公司可以在不同的平台上发布(Windows、Linux)上运行的 Java 虚拟机(JVM)——负责载入和执行 Java 编译后的字节码。那么这些 Java 字节码是什么样子的?让我们用一个简单的代码来看看。
源码如下:
package com.cmower.java_demo;
public class Test {
public static void main(String[] args) {
System.out.println("沉默王二");
}
}
代码编译通过后,通过 xxd Test.class 命令查看字节码文件。
xxd Test.class
00000000: cafe babe 0000 0034 0022 0700 0201 0019 ...4."...
00000010: 636f 6d2f 636d 6f77 6572 2f6a 6176 615f com/cmower/java_
00000020: 6465 6d6f 2f54 6573 7407 0004 0100 106a demo/Test...j
00000030: 6176 612f 6c61 6e67 2f4f 626a 6563 7401 ava/lang/Object.
00000040: 0006 3c69 6e69 743e 0100 0328 2956 0100 ...()V..
00000050: 0443 6f64 650a 0003 0009 0c00 0500 0601 .Code...
00000060: 000f 4c69 6e65 4e75 6d62 6572 5461 626c ..LineNumberTabl
也许你的第一感觉和我一样困惑。这些字节码中的数字和字母代表什么?让我们专注于这个字节码 cafe babe,它被称为“魔数”,是 JVM 识别 .class 文件的标志。定制文件格式的人可以自由选择魔法值(只要没有使用),例如 .png 文件的魔数是 8950 4e47。
追根溯源,我们来看看这些class文件的来源。除了常见的开发者在应用程序中编写的项目录下类别外,还有java内部的核心类别、java核心扩展类别和动态加载远程类别.class文件。此外,还有不同的加载器负责加载。
ClassLoader 是 Java 绝大多数类加载器都是从提供的类加载器中继承下来的 ClassLoader,它们被用来加载不同的来源 Class 文件。Java 运行的基本类别由一个名称组成 BootstrapClassLoader 负责加载的加载器也被称为 根加载器/引导加载器。注意,BootstrapClassLoader 比较特殊,不继承 ClassLoader,而是由 JVM 内部实现;java ExtensionClasloder的核心扩展 负责加载;开发人员在项目中编写的文件将由开发人员编写 AppClassLoader 加载加载器,它也被称作 系统加载器 System ClassLoader;如果要远程加载(本地文件)/网络下载)的方式必须定义自己 ClassLoader,复写其中的 findClass() 只有这样才能实现方法。
接下来,让我们介绍一下java类加载过程。Java 类加载过程可分为类加载过程 5 阶段:载入、验证、准备、分析和初始化。 5 一般是顺序发生的,但在动态绑定的情况下,分析阶段发生在初始化阶段之后。
1)Loading(载入)
JVM现阶段的主要目的是将字节码从不同的数据源转换为二进制字节流,并将其加载到内存中,生成代表此类的字节码 java.lang.Class 对象。
2)Verification(验证)
JVM将在这个阶段验证二进制字节流,只有符合JVM字节码规范才能验证 JVM 正确执行。这一阶段是保证JVM安全的重要屏障,因此将进行严格的检查和筛选。
3)Preparation(准备)
JVM 类变量(也称为静态变量)将在这个阶段对齐,static 修改关键字)分配内存并初始化(默认初始值对应数据类型,如 0、0L、null、false 等等。容易被忽视的是,static final 修饰变量称为常量,不同于类变量,一旦赋值,常量就不会改变。
4)Resolution(解析)
这一阶段将常量池中的符号引用转化为直接引用。
符号引用以一组符号(任何形式的字面量,只要在使用时能够无歧义地定位到目标)来描述引用的目标。
在编译时,Java 类不知道引用类的实际地址,所以只能用符号引用来代替。例如 com.Wanger 类引用了 com.Chenmo 类,编译时 Wanger 类并不知道 Chenmo 类的实际内存地址只能使用符号 com.Chenmo。
通过分析符号引用,直接引用可以找到引用的实际内存地址。
5)Initialization(初始化)
这一阶段是类加载过程的最后一步。在准备阶段,类变量已被赋予默认初始值,而在初始化阶段,类变量将被赋予代码期望赋予的值。换句话说,初始化阶段是实施类结构方法的过程。
父母指定模型:如果类加载器收到加载请求,将委托上加载器完成请求,上加载器将委托上加载器,直到顶加载器;如果上加载器不能完成类加载工作,当前类加载器将尝试自行加载类。
使用父母委派模式有一个明显的好处,那就是 Java 类与其类加载器具有优先级的层次关系,这是为了保证 Java 程序的稳定运行非常重要。如果两个类别的加载器不同,即使两个类别来自相同的字节码文件,这两个类别也必须不相等——双亲委派模型可以保证同一类别最终会被特定类别的加载器加载。
类加载方式是 Java 一项非常创新的技术为未来的热修复技术提供了可能性。我们只能通过不断的学习来掌握这项技术,并结合其他技术java开发技术,才能为java创造无限的未来。