代码编织梦想

熟悉的读者都知道,庆哥的文章一向是通俗易懂,直接要害,一看就知道,原来是这么回事,很多人表示,终于懂了,不信,咱们来看看JVM虚拟机中的一个重点知识-类加载


你要先搞清楚这些

类加载这个知识点在Java知识体系中属于哪个位置?,稍微熟悉一点的肯定知道,必须是Java虚拟机啊 ,是的没错,请记住这句话:

学Java必须学习Java虚拟机,JVM不会,你不是一个合格的Java程序员,被淘汰的可能性很大~

有人说了,咋回事,看你一篇文章,还没开始呢?我都不算是Java程序员了?

唉,毕竟我总为你们着想~


Java的类加载属于 JVM虚拟机的知识 一般在学习虚拟机的时候出现,平常基础学习可以先不了解~

啥意思呢?你环境变量还没搞清楚呢?还去弄类加载?不现实啊,不过你还别说,我在教一些人学Java的时候还真发现,好多人对环境变量这块还真是一知半解~

不信,我考你一个问题,俩吧,如下:

  1. 请问javac --version输出的是什么的版本?
  2. 再请问java --version输出的是什么版本?

很多人的基础其实真的差,比如你说下,Java中的引用数据类型包括哪四种?

懵x了吧?

咋又扯远了,不说了,讲回咱们的类加载,那么在学习类加载之前,请你一定记住如下这句话:

类加载核心目的:把存在本地的Java源代码经过javac编译生成的字节码文件(xxx.class 二进制文件)加载到内存中去,让其成为可执行状态,即可用

请把这读个十遍,理解个一二,咱们再继续~

这个时候,你要动脑筋思考了,为啥要加载啊?

然后再请你记住这么一句话:

**为啥要加载**:任何程序想要运行都需要加载进内存中

感觉到了没?看到没,这就是干货啊~

接下来,你还要再思考一个问题,就是这个加载操作谁来完成?谁去执行这个加载动作?

别想了,是JVM,具体就是用ClassLoader,也就是类加载器,也是一个类,不过由JVM去操作,对于我们来说,是无感的~ (加载进内存后还有很多操作)

走神了?可千万别,接下来的内容超级重要,啥嘞?

挺好了,字节码文件对应一个由JVM生成的Class对象,把这句话狠狠的记在心里,然后继续听我给你说:

Java源码和编译生成的字节码文件是我们可以真实看到的,也就是Hello.java和Hello.class,这个时候有一个很重要的知识,就是字节码文件加载进内存后会产生一个与之对应的Class对象,这个Class对象是真实存在的Class类(lang包下,final类型)的一个实例,这个对象主要由JVM去操作~

上面都是重点,没有废话,那好,到了这里,如果你的理解能力一绝的话,那上面的这些看懂了,就OK了,下面的基本上不用咋看~

but我觉得在看的各位,理解能力都是0.5绝,还差点意思,那就老实听着我给你继续唠一唠

要知道大概的步骤

到了这里,我们就直接上干货,首先,你要清楚,类加载的步骤,主要有这么个回事:

  1. 加载(领路人):找到字节码文件,将其送进内存中(对应的Class对象)
  2. 链接(又分为三个小阶段)
    1. 验证(过安检):就是看看你这个字节码正不正经,必须是安全可靠的字节码文件,比如检查魔数等
    2. 准备:为静态字段分配内存并设置默认值
    3. 解析:符号引用(一个代指,不知道具体在哪)转换为实际引用(具体的位置)
  3. 初始化:设置类的正确初始值,JVM开始初始化类(执行client方法),完成这不,类才真实成为可执行的状态~

也就是说,总计三个大步骤,一共是五步,我就习惯上说5步,也就是加载,验证,准备,解析,初始化,这五步操作,要闭着眼睛都能倒背如流~(倒背我没试过,要不你试试也不是不可以)

然后对于类加载的这五步,总结一下就是:

类加载通过三个大阶段,共计5个步骤,将类从外部加载进内存中,使其变得可用,也就是产生了一个Class对象!

以上依然是没有废话,每一个字,每一个词都很重要,务必仔细阅读理解,学到了,那就是你的~

这里有一个需要注意的就是加载,验证,准备和初始化这四个阶段的顺序是确定的,但是解析这一阶段就不一定了,它也有可能在初始化之后才开始,这是为了支持java的运行时绑定,另外以上这几个阶段是按顺序开始,但是可没有说按顺序结束,也就是他们一般情况下都是混杂着进行的

把每个步骤单独拎出来说一说

接下来,我们对每一个步骤再单独拿出来说一说,看一看,这几个步骤都是怎样的?重点清楚,每个步骤,到底干了啥?

加载阶段

这一阶段其实就是类加载器真正作用的阶段,这一阶段主要做的事情就是把字节码文件安全的放到内存中,工作就完成了,然后你还要特别清楚的一件事情就是,在这个阶段:

是将class文件读入内存,并为之创建一个Class对象

注意到重点了吧,产生了一个Class对象,这个一定记住了,把这个阶段干的事情稍微细化下就是:

(1):通过一个类的全限定名来获取定义此类的二进制流(啥玩意?理解成字节码文件就完事了

(2):将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

(3):在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口(重点)

然后稍微总结一下就是,这个加载阶段,它是一个过程,这个过程的结果就是将字节码文件(二进制表示)加载进内存中生成对应的一个Class对象,这个Class对象表示的就是该类的一个映射,就是在内存中对应的该类一个存在形式,我们通过操作这个Class对象来实现对该类的一些操作~

OK,注意理解,一定注意理解~

验证阶段

这个阶段,其实人家的名字就说的很清楚了,就是俩字验证,也就是说,在这个阶段里,主要就是确保加载的**类的正确性**,以防加载对虚拟机有危害的类。这样的话对安全的类就有一个评判标准,一般有如下验证步骤:

• 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。

• 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。

• 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

• 符号引用验证:确保解析动作能正确执行。

然后对于这一阶段,还有一个需要特别注意的地方就是:

验证这一阶段其实是非常重要的,是用来保证虚拟机的安全,但是这一阶段却不是必须的,什么意思呢?也就是说,当你确定你这个类是安全的,比如你经过反复验证,这个类符合虚拟机规范,很安全,就可以考虑采用Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备阶段

到了准备这个阶段,依然是需要着重搞清楚,在这个阶段里面主要是做了哪些事情?

那么在这个准备阶段,它到底干了哪些事情?

正式为类变量静态全局变量,也就是被static修饰的变量)分配内存并设置类变量默认值的阶段,这些变量所使用的内存都将在方法区中进行分配。

所以,看到重点了吗?注意了,就是为类变量赋值,而且是默认值,记着这点那对准备阶段来说就是OK的了~

解析阶段

接下来就来到解析这个阶段,先说好,解析这个阶段是需要费点脑力去理解一个重要概念的,同样,让我们先搞清楚,在这个阶段里面,主要做了什么事情?

其实很简单,在解析这个阶段,主要就是符号引用转换为直接引用重点就是搞清楚符号引用和直接引用是啥?

所以,重点已经出现了,就是你要理解什么是符号引用以及什么是直接引用,重点则是对符号引用的理解,我接下来所说的内容,就是为了让你明白,这个符号引用到底是个啥?

这里的符号引用什么意思呢?其实也就是字面量,也就是我们写的那些东西,比如我们写一个测试类叫做Test.java,那么这个文件中肯定有个类名叫做Test,这个“Test”就是个字面量,同时在虚拟机层面它就是个符号引用,这里将其转变成直接引用就是将其替换为其在内存中的内存地址。

要知道,我们写的代码,加载进内存中都是要有空间将其存储起来的,代码的每一个字母都是要存储的~

然后我们再继续往下理解,首先明确记住,符号引用就是字面量(你能看见的都叫做字面量),比如我们写的一个类文件中引用了另一个类,但是在javac编译成字节码文件的时候,并不知道引用的这个类在哪(还没分配内存地址,不知道具体位置),但是此时需要有个标志代指这个类,这个标志就是符号引用,这个符号引用是什么样的,不是随随便便命名的,有明确定义,在Java虚拟机规范的Class文件格式中。

就是字节码文件是有规范的,该怎样,要怎样,有哪些是有要求的,编译后的字节码文件中就含有相应的符号引用

不知道到了这里,你理解的怎样了?如果差点意思,那就再多读几遍~

其实符号引用强调的是编译成class文件之后,这个时候并不能确定一个类的引用到底指向谁,因此只能使用特定的符号代替,这就叫做符号引用,比如在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。

OK,以上就是对符号引用的解读了,那什么又是直接引用呢?简单直白的说,在类加载阶段,经过解析将符号引用解析成直接引用,也就成了指向一个具体目标的内存地址。

也就是说,你就可以理解为就是实际的内存地址

深入严格来说:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄,直接引用的目标一定在内存中存在。

初始化阶段

接着我们再看最后一个阶段,也就是初始化阶段,那这个阶段式怎么回事呢?

核心就是一个赋值,然后搞清楚:初始化阶段做了什么事情?什么情况下会触发初始化?

首先来看,做了什么事情:

  1. 给类的静态变量赋予实际的值,也你就是你设定的值,不再是默认值
  2. 执行静态代码块(静态代码块只能访问定义在静态代码块之前的静态变量,定义在静态代码块之后的静态变量,可以赋值,但是不能访问)

Java中,并不是一下子把所有的类都给加载完,而是用到谁加载谁,比如你程序中定义一个类,但是此时用不到,如果你用javac将其全部编译生成字节码文件,此时要知道,生成的字节码都是单独存在的,也就是这个暂时用不到的类也有个对应的字节码文件,那刚开始用不到就先不把它加载进内存,一旦用到它再去加载它,那这个时候就得搞清楚,什么时候才算用到它了,也就是什么时候会触发类的初始化,谈到初始化,其实就是在讲类的加载了有且只有五种):

1、遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的Java场景是:

  1. 关键字new实例化对象的时候,这个最常见也是你常用的了
  2. 读取或者设置一个类的静态字段的时候(注意,final修饰的定值会在编译器就把值设置好并放入到常量池)
  3. 还有就是,当你调用了一个类的静态方法

2、你使用了反射,也就是对类进行反射调用的时候

3、类之间存在继承关系的时候,当你初始化子类,发现其父类没有初始化,则先初始化其父类

4、还是有就是容易被忽视的虚拟机启动的时候,这个时候会初始化main方法所在的类

5、最后一个就是当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。(这个其实个人觉得,你先记住有这个情况就行,不必深究到底目前)

以上内容就是在初始化阶段,你需要了解的内容了~

其实谈到java的类加载,那肯定离不开类加载器的内容,接下来我们再说说这个类加载器相关的内容~

类加载器

类加载依靠的就是类加载器,也就是Classoader,类加载的过程有5步,加载,验证,准备,解析和初始化,实际上,我们能干预操作的也就只有加载,剩余的验证,准备,解析和初始化是JVM控制的,不让你碰~

我们需要重点理解知识点ClassLoader和双亲委派机制

Bootstrap ClassLoader

这个可以被叫做根加载器,启动类加载器,引导类加载器,反正都是它,关于这个加载器,我们需要了解的知识点如下:

  1. 主要负责加载核心类库:JDK\jre\lib下或者被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
  2. C++实现,JVM的一部分,看不到它的父类,JVM启动会自动运行的一个加载器,需要负责加载其他Java实现的加载器

OK,上述两点清楚了,这个根加载器就事OK的了~

ExtClassLoader

这个叫做扩展类加载器,由于jdk的一些升级,这个被什么平台类加载器替代啥的,这个我们先不管,对于这个扩展类加载器最基本的东西,你得理解,也就是如下这些内容:

  1. 主要加载:JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。
  2. Java语言实现,被根加载器加载

以上这些内容足矣~

AppClassLoader

我们写的类,主要就是使用这个加载器来加载了,叫做应用类加载器,是个默认加载器,关于它,你需要知道:

  1. 主要加载:用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
  2. Java语言实现,被根加载器加载

以上就是三个我们需要知道的类加载器,接下来依据这些类加载器,我们还需要了解的一个知识点就是双亲委派机制~

双亲委派

什么是双亲委派机制呢?它是这样说的:

默认自己不尝试加载,而是先交给自己的父类加载,父类无法加载,自己在加载,自己也加载不了就报异常,也就是AppClassLoader -> ExtClassLoader -> Bootstrap ClassLoader
这么一个走向~

所以你看,看似双亲委派听起来很高大上,其实概念是简单易懂的~

那这个时候你就得思考了,搞这么一个机制有什么用?不能是平白无故搞这么一个机制吧,存在即有理,关于双亲委派机制的好处,你要熟悉如下两点:

  1. 避免重复加载(如果父类加载器已经加载过这个类的话,子类加载器就不需要再次加载了)
  2. 安全性(java的核心api类库都是被启动类加载器加载的,如果外部突然要加载一个,一个比如java.lang.xxx的话,这个会被传到启动类加载器,启动类加载器发现这个已经加载过啦,所以不管你,直接返回已经加载的,这样就有效的防止核心api被篡改。)

你看,以上就是双亲委派机制你需要了解的知识点了~

接下来,再简单给大家扩展一些知识~

类加载器应用场景

整个类加载过程,我们可以在第一步加载这块做操作来实现一些功能,也就是通过自定义类加载器来解决一些实际问题,比如:

  1. 解决类的依赖冲突
  2. 实现热部署和热加载
  3. jar包的加密保护

解决类依赖冲突:
比如一个项目中的某个业务,本身用到了一个开源库(极简理解为一个类),然后又引用了一个其他的功能模块,这个功能模块本身也是用了这个开源库,只不过这个功能模块用的是这个开源库2.0版本,而业务本身用的是开源库1.0版本,区别就是类中的方法不一样,2.0版本多了方法,由于我们都是用Maven管理这些开源库,Maven的机制会使用引用路径最短的开源库为最终的那个,也就是使用业务本身的1.0版本,这就导致调用2.0版本中的方法找不到,报一个NoSuchMethodException异常~

不要想着统一版本,项目中的东西不是说升级就升级的,解决方案就是自定义类加载器,给这个功能模块自定义加载器去加载这个开源库~

热加载:
热加载的目的就是快速启动,比如我们开发中需要频繁重启应用来调试应用,但是项目的重启是很费时间的,这个时候可以采用热加载快速进行应用的启动。

热加载的方案比较多,比较推荐的是Spring官方的Spring boot devtools,核心理念就是重启不需要把所有的内容全部重新加载,而是只重新加载改动的内容,这个就可以通过自定义类加载器来完成~

热部署:
本质和热加载差不多,只更改变化的内容达到快速部署

加密保护:

这个实际上就是防止别人对我们的字节码文件反编译,我们可以使用自定义类加载机制实现加密保护~

核心就是把字节码打包的时候进行正向加密,然后通过自定义类加载器对加密后的包进行解密再按照字节码正常的加载过程去加载就行了~

至于如何加解密就属于加密算法这块,不深入研究

庆哥推荐学习方法

大家平常的技术学习,我觉得可以采用费曼学习法,不熟悉的可以去了解一下,就比如关于类加载这块的知识,我学了以后我还写文章,写博客教大家这个知识,而且,我为了巩固自己的学习效果,我还把它讲出来,正所谓,你知道了不一定能给别人讲懂,你能别人讲懂了那说明你是真的会了,所以,我还傻乎乎的把它录制出来,哈哈~
在这里插入图片描述
虽然累,但是效果好啊毕竟学到了才算自己的

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_33921105/article/details/129538087

【深入浅出jvm】——类加载过程_mandy_i的博客-爱代码爱编程

概念     虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。类加载流程:加载;(验证,准备,解析)三者统称为连接;初始化;使用;卸载。         类加载     通过一个类的全限定名来获取定义此类的二进制流     将这个字节流所代表的静态存储结构

【语言篇】java枚举类,字节码层面的深入浅出_grandachn的博客-爱代码爱编程

Java语言篇 Java枚举类枚举类的作用枚举类的工程实践枚举类字节码分析values()方法和ordinal()方法枚举类特性1.支持定义方法,同时支持方法的重写(Override)2.可实现接口,不可继承

java基础概念<最通俗易懂的讲解>_chetianyao8457的博客-爱代码爱编程

目录: 一、JDK 和 JRE 的区别? 二、final 与 static 的区别? 三、get和post的区别? 四、堆和栈的概念和区别  五、浅谈Java反射机制 一、JDK 和 JRE 的区别? JDK:java development kit (java开发工具) JRE:java ru

谁说深入浅出虚拟机难?现在我让他通俗易懂(JVM)-爱代码爱编程

1:什么是JVM 大家可以想想,JVM 是什么?JVM是用来干什么的?在这里我列出了三个概念,第一个是JVM,第二个是JDK,第三个是JRE。相信大家对这三个不会很陌生,相信你们都用过,但是,你们对这三个概念有清晰的知道么?我不知道你们会不会,知不知道。接下来你们看看我对JVM的理解。 想要了解更多Java架构技术的,可以关注我一下,我后续也会整理更多

深入浅出JVM(一)类加载-爱代码爱编程

文章目录 前言一、JVM简介二、类加载机制1.类加载器1.1 启动类加载器1.2 扩展类加载器1.3 应用程序类加载器2.类加载器工作机制(双亲委派机制)三、类加载的过程1、类的生命周期2、类的生命周期详解2.1、加载2.2、验证2.3、准备2.4、解析2.5、初始化总结 前言 JVM这三个字母,可以说是超高频出现在我们的开发生活,听上去很高

【最新版】Java基础视频精华版深入浅出(有源码)-爱代码爱编程

【最新版】Java基础视频精华版深入浅出 Java入门教程目录: 1.计算机的基础知识(day01) 2.Java语言基础(day02-day06) 3.Java面向对象(day07-day10) 4.Java开发工具(day11) 5.Java常见对象(day12-day14) 6.Java集合框架(day15-day19) 7.JavaIO流(day

深入浅出Java!一招彻底帮你搞定HashMap源码,分享面经-爱代码爱编程

开头 对于一个Java程序员而言,能否熟练掌握并发编程是判断他优秀与否的重要标准之一。因为并发编程是Java语言中最为晦涩的知识点,它涉及操作系统、内存、CPU、编程语言等多方面的基础能力,更为考验一个程序员的内功。 那到底应该怎么学习并发编程呢?**Java SDK的并发工具包有很多,是要死记硬背每一个工具的优缺点和使用场景吗?**当然不是,想要学好

深入浅出SpringCloud-爱代码爱编程

1. 环境介绍 IDEA 2019.3.4SpringBoot 2.0.1.RELEASESpring Cloud Finchley.M92. 微服务快速入门 2.1 应用架构对比 2.1.1 单体应用 单体应用架构 单体应用架构的优缺点 单体模式的优势 易于开发易于测试易于部署单体模式的不足 应用工程变得又大又复杂敏捷开发和部署

深入类加载机制_jerry_350的博客-爱代码爱编程

深入类加载机制 初识类加载过程 使用某个类时,如果该类的class文件没有加载到内存时,则系统会通过以下三个步骤来对该类进行初始化 1.类的加载(Load) → 2.类的连接(Link) → 3.类的初始化(Initi

深入浅谈单例模式_努力努力再努力c.的博客-爱代码爱编程

一、什么是单例模式 单例模式就是保证了一个类只有一个实例,并且提供了一个访问它的全局访问点。 二、单例模式的作用 单例模式其实主要就是为了解决一个全局使用的类被频繁地创建与销毁的问题。 我们都知道,通过类实例化的对象一般都是

java-网络编程-爱代码爱编程

网络编程 网络编程基本概念三要素IP地址端口协议 UDP通信程序发送数据接收数据UDP三种通讯方式单播组播广播 TCP通信程序发送数据(客户端)接收数据(服务端)完整案例:

设计模式之美-工厂模式-爱代码爱编程

分类:简单工厂模式(静态工厂模式),工厂方法模式,抽象工厂模式。 这种设计模式也是 Java 开发中最常见的一种模式,它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。 简单说就是为了提供代码结构的扩展性,屏蔽每一个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调用即可,同时,这也是去掉

【gof 23】篇2:工厂方法-爱代码爱编程

1. 什么是工厂方法? 工厂方法类似简单工厂,只不过将对象实例化延迟到了子类来实现。 工厂方法定义: 定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使得一个类的实例化延迟到其子类。 在父类不知道具体实现

*7 vue2 响应式原理-爱代码爱编程

vue2 响应式原理 1、对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(参考http://t.csdn.cn/OStv5)         - 问题:新增、删除属性,页面不更新         - 解决:                            Vue.set(位置,属性名,属

springsecurity使用数据库的用户数据做认证-爱代码爱编程

SpringSecurity使用UserDetailsService 接口来获取用户信息UserDetails ,根据UserDetails 的 getPassword() 方法,判断用户名密码是否正确。大致流程如下: 框架默认情况使用InMemoryUserDetailsManager ,即把用户信

案例01-爱代码爱编程

目录 一:背景介绍 二:思路&方案 三:过程 1.修改数据没有删除缓存  添加pom.文件的依赖  通过班级id查询课程名称 执行结果  通过班级id修改课程名称(没有删除redis中的缓存) 执行结果  修改完毕之后我们在去执行一下通过班级id查询 2.修改数据删除了缓存 在原来修改数据的方法里添加了删除缓存的代码 四:

教你如何10分钟快速在windows中安装mysql_10分钟anz mysql-爱代码爱编程

我是 ABin-阿斌:写一生代码,创一世佳话,筑一览芳华。 如果小伙伴们觉得我的文章不错,记得一键三连哦 声明:mysql安装原文作者:浪啦啦啦啦啦 文章目录 前言第一步:下载安装包第二步:解压,

jdbc视频学习笔记_"string url = \"(4) ://localhost:3306/db1?(5) =gmt-爱代码爱编程

学习视频:尚硅谷JDBC核心技术视频教程(康师傅带你一站式搞定jdbc)_哔哩哔哩_bilibili 目录 JDBC概述 1.1 数据的持久化 1.2 Java中的数据存储技术  1.3 JDBC介绍          1.4 JDBC体系结构 1.5 JDBC程序编写步骤  获取数据库连接 2.1:Driver 接口实现类