JVM-(一)类加载子系统
本文最后更新于:May 13, 2023 pm
积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里,不积小流无以成江海。齐骥一跃,不能十步,驽马十驾,功不在舍。面对悬崖峭壁,一百年也看不出一条裂缝来,但用斧凿,能进一寸进一寸,能进一尺进一尺,不断积累,飞跃必来,突破随之。
目录
JVM整体结构图
类加载子系统
类加载器子系统负责从文件系统或者网络中加载Class文件。Class文件在文件开头有特定的文件标识,用来标识是否是一个能被虚拟机接受的Class文件。它是每个Class文件的头4个字节组成,称为魔数。
ClassLoader只负责Class文件的加载,至于是否可以运行,则由Execution Engine(执行引擎)决定。
加载的类信息存放在方法区中。方法区中除了类信息外,还会存放
运行时常量池
(对应Class文件中的常量池。当Class文件中的常量池运行时加载到内存里,就是运行时常量池。)信息。
类的加载过程
类的加载过程分为三步:加载->链接->初始化。其中,链接
又分为:验证->准备->解析。
加载
- 通过全类名获取定义此类的二进制字节流。
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
- 在内存中生成一个代表该类的
java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。
加载Class文件的方式:
- 从本地系统中直接加载。
- 通过网络获取,如:Web Applet。
- 从zip压缩包中读取,成为日后jar、war格式的基础。
- 运行时计算生成,如:动态代理。
- 由其他文件生成,如:JSP。
- 从加密文件中获取,典型的防Class文件被反编译的保护措施。
链接
分为三部分:验证->准备->解析。
验证
- 目的在于确保Class文件的字节流中包含的信息符合当前虚拟机的要求,保证被加载类的正确性,确保不会危害虚拟机自身的安全。
- 主要包括四种验证:文件格式验证、元数据验证、字节码验证、符号引用验证。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值(默认值,即零值)的阶段,这些内存都将在方法区中分配。
- 这时候进行内存分配的仅包括类变量( Class Variables ,即静态变量,被
static
关键字修饰的变量,只与类相关,因此被称为类变量),而不包括实例变量。实例变量会在对象实例化时随着对象一块分配在 Java 堆中。 - 不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化。
- 这里所设置的初始值”通常情况”下是数据类型默认的零值(如 0、0L、null、false 等),比如我们定义了
public static int value=111
,那么 value 变量在准备阶段的初始值就是 0 而不是 111(初始化阶段才会赋值)。特殊情况:比如给 value 变量加上了 final 关键字public static final int value=111
,那么在准备阶段 value 的值显示的就是 111。
解析
将常量池内的符号引用转换为直接引用的过程。解析操作往往在JVM执行完初始化之后再执行。
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符 7 类符号引用进行。
- 符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
初始化
初始化阶段是执行初始化方法 <clinit> ()
方法的过程,是类加载的最后一步,这一步 JVM 才开始真正执行类中定义的 Java 程序代码(字节码)。(<clinit> ()
方法是编译之后自动生成的。)
对于<clinit> ()
方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 <clinit> ()
方法是带锁线程安全。
- 此方法只会在类中有静态变量的赋值或者静态代码块时,才会有此方法。
- 若该类具有父类,JVM会保证子类的
<clinit> ()
执行前,父类的<clinit> ()
已经执行完毕。
类加载器
JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader
。
- BootstrapClassLoader(启动类加载器) :最顶层的加载类,由 C++实现,负责加载
%JAVA_HOME%/lib
目录下的 jar 包和类或者被-Xbootclasspath
参数指定的路径中的所有类。 - ExtensionClassLoader(扩展类加载器) :主要负责加载
%JRE_HOME%/lib/ext
目录下的 jar 包和类,或被java.ext.dirs
系统变量所指定的路径下的 jar 包。 - AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
如下:
1 |
|
启动类加载器
BootstrapClassLoader(启动类加载器)
。
- 使用C/C++实现,嵌套在JVM内部。
- 用来加载Java的核心库,用于提供JVM自身需要的类。
- 没有父加载器。所以,不继承自java.lang.ClassLoader。
- 是加载扩展类(ExtensionClassLoader)和应用程序类加载器(AppClassLoader)的父类加载器。
- 出于安全考虑,
BootstrapClassLoader
启动类加载器只加载包名为java
、javax
、sun
等开头的类。
双亲委派机制
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行;
- 如果父类加载器也还存在父类加载器,则也会进行向上委托,请求最终会到达顶层的启动类加载器;
- 到顶之后,从最高加载器开始。如果父类加载器可以完成类加载任务,就成功返回;如果父类加载器无法完成此加载任务,则子加载器才会尝试自己去加载。
双亲委派模型的好处:
- 保证了 Java 程序的稳定运行,可以避免类的重复加载。也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为
java.lang.Object
类的话,那么程序运行的时候,系统就会出现多个不同的Object
类。
沙箱安全机制
就是双亲委派机制可以:保证对Java核心源代码的保护,这就是沙箱安全机制。
判断两个Class对象为同一个类的必要条件。
- 类的全限定名称相同。包括包名。
- 加载类的ClassLoader(指ClassLoader实例对象)必须相同。
类的主动使用和被动使用
除了以下情况外,均为类的被动使用,被动使用不会导致类的初始化。
- 创建类的示例。
- 访问某个类或接口的静态变量,或者对该静态变量赋值。
- 调用类的静态方法。
- 反射(Class.forName(“com.tothefor.P”))
- 初始化一个类的子类
- Java虚拟机启动时被标明为启动类的类。
- JDK7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化。
本文作者: 墨水记忆
本文链接: https://tothefor.com/DragonOne/f01f1fd6.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!