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

  1. BootstrapClassLoader(启动类加载器) :最顶层的加载类,由 C++实现,负责加载 %JAVA_HOME%/lib目录下的 jar 包和类或者被 -Xbootclasspath参数指定的路径中的所有类。
  2. ExtensionClassLoader(扩展类加载器) :主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类,或被 java.ext.dirs 系统变量所指定的路径下的 jar 包。
  3. AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.tothefor;

import sun.misc.Launcher;

import java.net.URL;
import java.util.Properties;

/**
* @Author DragonOne
* @Date 2022/6/4 16:13
* @墨水记忆 www.tothefor.com
*/
public class study_01 {

public static void main(String[] args) {
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for(URL it : urLs){
System.out.println(it);
}
System.out.println("==========================================");
String property = System.getProperty("java.ext.dirs");
System.out.println(property);
}
}

//输出
file:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/lib/resources.jar
file:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/lib/rt.jar
file:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/lib/sunrsasign.jar
file:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/lib/jsse.jar
file:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/lib/jce.jar
file:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/lib/charsets.jar
file:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/lib/jfr.jar
file:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/classes
==========================================
/Users/dragonone/Library/Java/Extensions:/Users/dragonone/Library/Java/JavaVirtualMachines/corretto-1.8.0_292/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

启动类加载器

BootstrapClassLoader(启动类加载器)

  • 使用C/C++实现,嵌套在JVM内部。
  • 用来加载Java的核心库,用于提供JVM自身需要的类。
  • 没有父加载器。所以,不继承自java.lang.ClassLoader。
  • 是加载扩展类(ExtensionClassLoader)和应用程序类加载器(AppClassLoader)的父类加载器。
  • 出于安全考虑,BootstrapClassLoader启动类加载器只加载包名为javajavaxsun等开头的类。

双亲委派机制

  • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行;
  • 如果父类加载器也还存在父类加载器,则也会进行向上委托,请求最终会到达顶层的启动类加载器;
  • 到顶之后,从最高加载器开始。如果父类加载器可以完成类加载任务,就成功返回;如果父类加载器无法完成此加载任务,则子加载器才会尝试自己去加载。

双亲委派模型的好处:

  • 保证了 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 协议 ,转载请注明出处!