JVM-02 类加载子系统

基于《深入理解JVM》输出目录

JVM-01 概述
JVM-02 类加载子系统
JVM-03 运行时数据区- [程序计数器+虚拟机栈+本地方法栈+本地方法+堆+方法区]
JVM-04 执行引擎+字符串常量池StringTable
JVM-05 垃圾回收(器)



前言

提示:基于《深入理解JVM》-第二版-周志明著的个人输出文章,文中会提及相关内容在该书页码。B站大学相关视频 链接地址参考大佬博客链接地址


1. 类加载子系统 / JVM类加载机制

书P209: 虚拟机类加载机制

1.1 JVM架构图

JVM架构图


1.2 类加载子系统作用

  1. 负责从文件系统或者网络中加载Class文件,class文件在文件开头要有特定的文件标识验证阶段-文件格式验证
  2. ClassLoader只负责加载class文件,至于是否能运行由执行引擎(Execution Engine)决定
  3. 加载的类信息存放在一块成为方法区的内存空间。除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字量(这部分常量信息是class文件中常量池部分的内存映射

1.3 类加载过程

类加载过程

1.3.1 加载(Loading)

  1. 通过一个类的全限定名称来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
    数组类的加载过程有点特殊,建议看书

1.3.2 链接(Linking)

I. 验证
  • 目的在于确保Class文件的字节流中的信息符合当前虚拟机的要求,保证被加载类的正确性,不会危害虚拟机自身安全
主要有四种验证:
  1. 文件格式验证:是否符合Class文件格式的规范
    是否以魔数0xCAFEBABE开头
    主次版本号是否在当前虚拟机处理范围内)
  1. 元数据验证:对字节码描述的信息进行语义分析,确保信息都符合Java规范
    -这个类是否有父类(除了java.lang.Object之外的类都应当有父类
    -这个类的父类是否继承了不允许被继承的类(final修饰的类)
    -如果不是抽象类,是否实现了其父类或接口要求实现的所有方法
  1. 字节码验证:在对元数据信息的数据类型做完校验之后,这阶段将对类的方法体进行校验分析,保证这个类的方法在运行是不会危害虚拟机安全
    -保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作
    E:操作数栈中放了int类型,使用时却按long类型来加载入本地变量表,就有问题
    -保证跳转指令不会跳到方法体以外的字节码指令上
  1. 符号引用验证:发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段(解析)中发生。看作对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验
    -符号引用中通过字符串描述的全限定名称是都能找到对于的类
    -符号引用中的类、字段、方法的访问性(private、public…)是否可以被当前类访问
    -…
II. 准备
  • “通常情况”下,正式为被static修饰的变量分配内存并设置该类变量的默认初始值,即零值
数据类型零值数据类型零值
int0booleanfalse
long0Lfloat0.0f
short(short)0double0.0d
char‘\u0000’referencenull
byte(byte)0

E: public static int value = 123; 在准备阶段默认初始化零值,即0

  • “特殊情况”下,如果存在常量(用final修饰的static),因为final在编译的时候就会分配内存了,所以在准备阶段虚拟机将会根据常量的设置直接赋值。
    E: public static final int value = 123;
    编译时Javac会将value生成ConstantValue属性,在准备阶段JVM会根据ConstantValue的设置将value赋值为123,即final和static修饰的变量 在准备阶段会直接赋值
  • 对于实例变量将会在对象实例化时随着对象一起分配到Java堆中

没有static的变量呢?private int i = 1;private static int i = 1; 的区别?

  • 被赋值的时机来解释:
  • static int i是静态的类变量,当类加载的准备阶段时,i变量就会被分配内存地址和初始化为零值
  • 没有static的int i是私有的实例变量,只有在生成对象后来调用它的时候,才在类加载的初始化阶段来赋予其值:1。
III. 解析(有点难理解)
  1. 将常量池内的符号引用转化为直接引用的过程,解析操作往往在完成初始化阶段后再执行
  2. 符号引用:以一组符号来描述所应用的目标。可以是任意形式的字面量,定位得到目标即可
  3. 直接引用:直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
  4. 主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池 的CONSTANT_Class_info/CONSTANT_Fieldref_info…

1.4 初始化

  1. 初始化阶段就是执行类构造器方法<clinit>() 的过程
  2. 该方法是javac编译器自动收集类中的所有类变量的赋值动作静态代码块中的语 句合并而来。 如果没有静态变量,字节码文件中就不会有clinit方法。

图片1
图片2

  1. 构造器方法中指令按语句在源文件中出现的顺序执行
    在这里插入图片描述
    在这里插入图片描述

所以输出是num = 2; number = 10;这个是clinit方法收集顺序按源文件出现顺序决定的

  1. <clinit>() 方法与类的构造函数(<init>()方法 )不同;
    若该类具有父类,JVM会保证在子类的<clinit>() 方法执行之前,父类的<clinit>() 已经执行完毕。
    所以JVM中第一个被执行<clinit>() 方法的类肯定是java.lang.Object。
  2. 父类的<clinit>() 方法会先执行,意味父类中定义的静态语句块优先于子类的变量赋值操作
  3. 接口中不能使用静态语句块,但还是有变量初始化的操作。执行接口的<clinit>()方法不需要先执行父接口<clinit>()方法。
    只有父接口定义的变量使用时才会初始化。
    接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
  4. JVM必须保证一个类的<clinit>() 方法在多线程下被同步加锁

在这里插入图片描述


2. 类加载器分类

  1. JVM支持俩种类型的加载器:引导类(BootStrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
  2. 概念上来讲,自定义类的加载器一般指的是开发人员自定义的,但是JVM将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。
  3. 无论怎么划分,最常见的类加载器始终只有三个
    启动类加载器(引导类加载器,BootStrap ClassLoader)
    拓展类加载器(Extension ClassLoader)
    应用程序类加载器(App ClassLoader / 系统类加载器,System ClassLoader )

2.1自定义类与核心类库的加载器:

  1. 对于用户自定义类来说:使用系统类加载器AppClassLoader加载
  2. Java核心类库都是使用引导类加载器BootStrapClassLoader加载的

在这里插入图片描述


2.2 JVM自带的类加载器

在这里插入图片描述

启动类加载器(引导类加载器BootStrap ClassLoader)

  1. 由C++实现的,嵌套在JVM内部
  2. 加载java核心库(JAVA_HOME/jre/lib/rt.jar/resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类。
  3. 并不继承自java.lang.ClassLoader,没有父加载。是拓展类加载器和应用程序类加载器的父加载器
  4. 处于安全考虑,BootStrap启动类加载器只加载包名为java、javax、sun等开头的类

拓展类加载器(Extension ClassLoader)

  1. java语言编写 ,独立于JVM外部,由sun.misc.Launcher$ExtClassLoader实现。
  2. 派生于ClassLoader类,父类加载器为启动类加载器
  3. java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会由拓展类加载器自动加载

应用程序类加载器(系统类加载器,AppClassLoader)

  1. java语言编写,独立于JVM外部,由sun.misc.Launcher$AppClassLoader实现。
  2. 派生于ClassLoader类,父类加载器为拓展类加载器
  3. 它负责加载环境变量classpath或系统属性 java.class.path指定路径下的类库
  4. 该类加载器是程序中默认的类加载器,一般来说,java应用的类都是由它来完成加载
  5. 通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器

Java日常应用程序开发中,类的加载几乎都是上述3种类加载器相互配合执行的,必要时,我们还可以自定义类加载器,来定制类的加载方式。


2.3为什么要自定义类加载器

  1. 隔离加载类
  2. 修改类加载的方式
  3. 拓展加载源
  4. 防止源码泄漏

3. 双亲委派机制

工作过程:

如果一个类加载器收到类类加载的请求,它不会先自己去尝试加载,而是把这个请求委托给父类的加载器去执行,每一层次的类加载器都是如此。

所以所有的类加载请求都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(即它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去加载。

E:小孩拿着梨,先问老妈吃不,老妈问奶奶吃不,奶奶不吃再给老妈,老妈不吃再给小孩。要是有人想吃就轮不到下一个人。

E: 创建一个类名为String,在包名为java.lang下
E1
如图,虽然我们自定义了一个java.lang包下的String尝试覆盖核心类库中的String。但是由于双亲委派机制,启动加载器会加载java核心类库的String类(BootStrap启动类加载器只加载包名为java、javax、sun等开头的类),而核心类库中的String并没有main方法,所以报错。

3.1 双亲委派机制的优势

  1. Java类随着类加载器一起具备了一 种带优先级的层次关系。能避免类的重复加载
  2. 保护程序安全,防止核心API被随意篡改
    E1: 自定义类:java.lang.String
    E2: 自定义类:java.lang.MeDsh(java.lang包需要访问权限,阻止我们用包名自定义类)
    在这里插入图片描述

3.2 反向双亲委派机制(了解)

在这里插入图片描述

E:程序需要用到SPI接口实现类,通过双亲委派机制去到引导类加载器,加载rt.jar包的SPI核心类。该类中存在一些接口,需要用到具体的实现类用到第三方的jar包,不属于引导类加载器,所以是反向委派到系统类加载器去加载,实际上是线程上下文类加载器去加载jdbc.jar


4. 沙箱安全机制

自定义String类,但是在加载自定义的String类的时候回率先使用引导类加载器加载,而引导类加载器在加载过程中会先加载jdk自带的文件(rt.jar包中的java\lang\String.class),报错信息说没有main方法就是因为加载的是rt.jar包中的String类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制
E:读写U盘的时候的360沙箱,防止U盘内的病毒对沙箱外的系统构成污染。


5. 其他补充

5.1 类与类加载器

  1. 在JVM中表示俩个class对象是否为同一个类,存在俩个必要条件
    a) 类的完整类名和包名必须一致
    b) 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同
  2. 即,在JVM中,即使俩个类对象来源于同一个Class文件,被同一个虚拟机加载,但只要加载它们的ClassLoader实例对象不同,那么这俩个类对象也是不相等的。

5.2 类的主动使用和被动使用

  • Java程序对类的使用方式分为:主动使用 和 被动使用

主动使用:七种情况

  1. 创建类的实例
  2. 访问某个类或接口的静态变量,或者对静态变量赋值
  3. 调用类的静态方法
  4. 反射 比如Class.forName(com.dsh.jvm.xxx)
  5. 初始化一个类的子类
  6. Java虚拟机启动时被标明为启动类的类
  7. JDK7开始支持的动态语言:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、 REF_invokeStatic句柄对应的类没有初始化,则初始化

除了以上七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的初始化。


基于《深入理解JVM》输出目录

JVM-01 概述
JVM-02 类加载子系统
JVM-03 运行时数据区- [程序计数器+虚拟机栈+本地方法栈+本地方法+堆+方法区]
JVM-04 执行引擎+字符串常量池StringTable
JVM-05 垃圾回收(器)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值