艺考网
全国站

JVM知识点深入理解JVM类加载

xunaa
2024-10-07 07:07:55
编辑说
前言:
正如前面提到的,Java 程序实际上是。类文件被放入JVM并运行。虚拟机将描述类的数据从Class文件加载到内存中,并对数据进行验证、转换、解析和初始化,最终形成虚拟机可以

前言:

正如前面提到的,Java 程序实际上是。类文件被放入JVM并运行。虚拟机将描述类的数据从Class文件加载到内存中,并对数据进行验证、转换、解析和初始化,最终形成虚拟机可以直接使用的Java类型。这就是JVM的类加载机制。

一、类加载的过程

类从被加载到虚拟机内存开始,直到从内存中卸载。生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。

1. 负载

通过类的全限定名获取定义该类的二进制字节流(没有指定二进制字节流应该从Class文件中获取,可以从ZIP包中读取、从网络获取、计算并在运行时生成等)将这个字节流表示的静态存储结构转换为方法区的运行时数据结构,并在内存中生成表示该类的java.lang.Class对象,作为访问方法区中该类的各种数据的入口。完成后,虚拟机外部的二进制字节流按照虚拟机要求的格式存储在方法区中。这里稍微了解一下对象和类的概念。对象是实例化的类。类信息存储在方法区,对象存储在Java堆中。类是对象的模板,对象是类的实例。这里主要讲类别。

2. 验证

加载阶段尚未完成,连接阶段已开始。两者之间将会有交叉。由于Class文件可以通过任何方式生成,因此字节流不符合Class文件格式的约束,虚拟机会抛出java.lang.VerifyError异常。因此,为了保护虚拟机,JVM会进行以下四个方面的验证。

文件格式验证:验证class文件结构

元数据验证:这个是否有父类,父类是否继承了不允许继承的类等。

字节码验证:验证类的方法体。 JDK1.6之后,只需要检查StackMapTable属性中的记录是否合法即可。 JDK1.7之后,对于主版本号大于50的Class文件,使用类型检查来完成数据流转。分析

符号引用验证:完全限定名是否能找到对应的类,以及指定类中是否存在与方法的字段描述和简单名称描述相匹配的方法和字段。可访问性是正确的。如果验证不成功,则会抛出java.lang.incompleteClassChangeError 异常的子类。

3. 准备

正式为类变量分配内存并设置类变量初始值的阶段。这些变量使用的内存将分配在方法区中。此时内存分配只包括类变量(被static修饰的变量),不包括实例变量。实例变量在对象实例化时与对象一起分配在Java堆中。此时赋予的初始值为零。假设一个变量定义为:public static int value=123;那么变量的初始值应该是0,而不是123。为123赋值的putstatic指令在程序编译后被存储在类构造函数clinit()方法中,所以为123赋值的动作不会被执行直到初始化阶段。如果字段属性表中存在ConstantValue属性,则在准备阶段该值将被分配给ConstantValue属性指定的值。例如:public static Final int value=123;然后在准备阶段,该值将被赋值为123;

4. 分析

将符号引用转换为直接引用的过程。符号引用是描述引用目标的一组符号。该符号可以是任何形式的文字,只要它可以用来明确地定位目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标不一定会加载到内存中。直接引用可以是直接指向目标的指针、相对偏移量或者可以间接定位目标的句柄。直接引用与虚拟机实现的内存布局有关。同一符号引用在不同虚拟机实例上翻译出来的直接引用一般是不一样的。如果存在直接引用,则引用的目标必须已经存在于内存中。

JVM知识点深入理解JVM类加载

解析动作主要针对类或接口、字段(类成员变量)、类方法、接口方法等的引用进行。

类或接口解析:判断要转换的直接引用是数组类型的引用还是普通对象类型的引用,从而进行不同的解析。

字段解析:解析字段时,会首先在该类中查找是否包含简单名称和字段描述符与目标匹配的字段。如果是,则搜索结束;如果没有,则按照继承关系从上面开始。递归地向下搜索该类实现的每个接口及其父接口。如果没有该接口,则根据继承关系从上到下递归查找其父类,直至查找完成。搜索过程如下图所示:

最后请注意:理论上是按照上述顺序进行搜索和分析的,但在实际应用中,虚拟机的编译器实现可能比上述规范更严格。如果同名字段同时出现在类的接口和父类的接口中,或者出现在自身或父类的接口中,编译器可能会拒绝编译。

类方法解析:类方法解析的搜索步骤与字段解析的搜索步骤类似,只不过多了一个判断方法是类还是接口的步骤,而类方法的匹配搜索是先搜索父类,然后搜索接口。

接口方法解析:与类方法解析步骤类似,由于接口不会有父类,因此只需递归向上查找父接口即可。

5. 初始化

这是类加载过程的最后一步。到了这个阶段,类中定义的Java程序代码才真正开始执行。在准备阶段,类变量已经被赋予了系统所需的初始值一次,而在初始化阶段,类变量和其他资源则按照程序员通过程序指定的主观计划进行初始化。在初始化阶段,类中的Java程序代码才真正开始执行。即初始化阶段是执行类构造函数的clinit()方法的过程。

clinit()方法解析过程: clinit方法是由编译器自动收集类中所有类变量的赋值动作并合并静态语句块(static{}块)中的语句生成的。编译器的顺序由源代码中的语句决定。文件中出现的顺序是确定的,因此静态语句块只能访问静态语句块之前定义的变量,而其之后定义的变量可以在前面的静态语句块中赋值,但不能访问。虚拟机确保在子类的clinit() 方法执行之前已经执行了父类的clinit() 方法。因此,虚拟机中执行的clinit()方法的类必须是java。郎。目的。 clinit() 方法对于类或接口不是必需的。如果类中没有静态语句块或对变量进行赋值操作,则编译器不需要为该类生成clinit() 方法。所以这个clinit()方法主要是用来给静态变量赋值以及执行静态语句块。接口中不能使用静态语句块,但仍然有变量初始化的赋值操作。因此,接口和类都会生成clinit()方法,但接口和类的区别在于,执行接口的clinit()方法不需要先执行父接口。 clinit() 方法。仅当使用父接口中定义的变量时,才会初始化父接口。另外,接口的实现类在初始化时不会执行接口的clinit()方法。

虚拟机确保类的clinit() 方法在多线程环境中正确锁定和同步。如果多个线程同时初始化一个类,则只有一个线程会执行该类的clinit() 方法,其他线程需要阻塞等待,直到活动线程完成执行clinit() 方法。如果类的clinit() 方法中存在长时间运行的操作,则可能会导致多个进程被阻塞。在实际应用中,这种阻塞往往是非常隐蔽的。

概括:

目前加载阶段是获取类信息,验证阶段是验证类是否符合JVM标准。这两个阶段可以交叉进行。然后需要在准备阶段给类分配内存空间,设置常量初值,初始化静态变量等。解析阶段就是将符号引用转换为直接引用,让类在类上有相应的内存。 JVM。最后一个阶段是初始化阶段,即执行类中的静态变量赋值语句和静态语句块。这里已经为该类完成了一些基本工作,可以使用了。

二、Java中的类加载器与双亲委派机制

实现“通过类的权限命名获取描述该类的二进制字节流”动作的代码模块称为类加载器。 Java类的加载是由虚拟机完成的。虚拟机加载描述类,将Class文件加载到内存中,并对数据进行验证、解析和初始化,最终得到一个java类型可以直接被java虚拟机使用。这就是虚拟机的类加载机制。 JVM中用来完成上述功能的具体实现就是类加载器。类加载器读取。 class字节码文件,将其转换为java.class字节码文件。郎。 Class 类的一个实例。每个实例都用来代表一个java类。可以通过实例的newInstance()方法创建该类的对象。

1.引导类加载器

这个类加载器负责将JAVA_HOME\lib目录下的类库加载到虚拟机内存中,用于加载java的核心库。该类加载器不是继承自java.lang.ClassLoader,不能被java程序直接调用。该代码是使用C++ 编写的。是虚拟机本身的一部分

JVM知识点深入理解JVM类加载

2.扩展类加载器

该类加载器负责加载JAVA_HOME\lib\ext目录下的类库,用于加载Java扩展库。开发者可以直接使用这个类加载器。

3.应用程序类加载器

这个类加载器负责加载用户类路径(CLASSPATH)下的类库。一般我们写的java类都是通过这个类加载器来加载的。这个类加载器是CLassLoader中getSystemClassLoader()方法的返回值,因此也称为系统类加载器。通常这是系统默认的类加载器

4. 自定义类加载器

这个加载器可以满足我们对加载类的特殊需求。需要继承java.lang.ClassLoader类,并重写其中的findClass()方法和defineClass()方法。

5. 家长委托机制

上面的四个加载器不是并行加载的。它们是通过JVM 中的双亲委派机制加载的:

双亲委派模型是组织类加载器之间关系的规范。它的工作原理是,如果一个类加载器收到类加载请求,它不会尝试加载类本身,而是将请求委托给父类加载器来完成,这样,所有的加载请求最终都会通过到顶级启动类加载器。只有当父类加载器无法完成加载请求时(其搜索范围没有找到所需的类时),才会交给子类加载器尝试加载。这样做的好处是:java类与其类加载器一起具有优先级的层次关系。这是非常有必要的,比如java.lang.Object,它存放在\jre\lib\rt中。 jar,它是所有java类的父类,所以无论加载哪个类,都必须加载这个类。最后将所有的加载请求汇总到顶层启动类加载器中。 Object类将由启动类加载器加载,因此加载相同的类。如果不使用双亲委派模型,每个类加载器自己加载,那么系统中就会出现多个Object类,应用程序就会一片混乱。

三、必须对类进行初始化的5种情况

1.遇到new、getstatic、putstatic、invokestatic字节码指令。最常见的Java代码场景是:使用new实例化对象、读取或设置类的静态字段(经过final修饰的静态字段或结果已被编译器放入常量池的静态字段除外)、调用静态方法一个班级的

2.使用java.lang.reflect包的方法对类进行反射调用

3、类继承父类,父类必须先初始化。

4.启动虚拟机并初始化主类

用户评论

ゞ香草可樂ゞ草莓布丁

终于找到一篇细致讲解JVM类加载的文章了!我一直对这个概念比较困惑,希望这篇博客能让我彻底弄明白~

    有10位网友表示赞同!

若他只爱我。

这篇文章的例子很形象,一下子就理解了双亲委派机制的作用。

    有12位网友表示赞同!

清原

看了标题就知道讲的是知识点了,期待深入浅出地讲解JVM类的加载过程!

    有6位网友表示赞同!

情字何解ヘ

了解JVM内部机制是学习Java开发的关键一步,对类加载机制有了更清晰的认识。

    有12位网友表示赞同!

←极§速

学习JVM总是要从基础开始,这篇文章正好打下一个很好的基础!

    有11位网友表示赞同!

ˉ夨落旳尐孩。

双亲委派机制还是挺绕口,希望能有更多的图文解释一下。

    有12位网友表示赞同!

掉眼泪

好喜欢这种深入浅出的讲解方式,容易理解。JVM真是个复杂的东西,需要好好学习!

    有7位网友表示赞同!

不离我

加载器、内存区域...这篇文章把关键概念都提到了。

    有18位网友表示赞同!

淡抹丶悲伤

希望详细解析一下常量池的用途和实现机制吧,太好奇了!

    有7位网友表示赞同!

旧爱剩女

这篇博文简直是JVM学习者的宝藏呀,收藏了!

    有19位网友表示赞同!

初阳

终于让我捋清了类加载的过程~

    有8位网友表示赞同!

人心叵测i

每次看到双亲委派机制就頭疼,这篇文章能不能解释得通俗易懂一点?

    有20位网友表示赞同!

丢了爱情i

看来要好好回顾一下Java虚拟机的工作原理了,这篇博文是个很好的参考。

    有10位网友表示赞同!

沐晴つ

学习Java真是需要不断深挖知识

    有11位网友表示赞同!

强辩

JVM的类加载机制的确很重要,理解它对后续学习很有帮助!

    有8位网友表示赞同!

*巴黎铁塔

希望能有更多类似的深入讲解的文章!

    有5位网友表示赞同!

关于道别

好文章,收藏了! 这篇文章写的真不错,深入浅出,容易理解。感谢作者分享!

    有9位网友表示赞同!

容纳我ii

双亲委派机制的确是JVM中一个重要的概念!

    有6位网友表示赞同!

免责声明
本站所有收录的学校、专业及发布的图片、内容,均收集整理自互联网,仅用于信息展示,不作为择校或选择专业的建议,若有侵权请联系删除!

大家都在看

JVM知识点深入理解JVM类加载

JVM知识点深入理解JVM类加载

前言: 正如前面提到的,Java 程序实际上是。类文件被放入JVM并运行。虚拟机将描述类的数据从Class文件加载到内存中,并对数据进行验证、转换、解析和初始化,最终形成虚拟机可以
2024-10-07
构造函数和析构函数

构造函数和析构函数

概念: 构造函数是用于创建对象的特殊成员函数。 影响: 为对象分配空间 为数据成员分配初始值 请求额外资源 特征: 创建对象时,系统自动调用构造函数,不能在程序中直接调用。 构造
2024-10-07
SOLIDWORKS提示:无法创建toolboxlibrary对象解决办法

SOLIDWORKS提示:无法创建toolboxlibrary对象解决办法

事实上,这个问题的发生是因为微软的KB 3072630补丁。网上有卸载补丁的解决方案,但是有些系统无法卸载这个补丁。只能用这个万能的方法来解决。第一步:先关闭solidworks软件,然
2024-10-07
现代C++ 移动构造、移动赋值、复制构造、复制赋值

现代C++ 移动构造、移动赋值、复制构造、复制赋值

c++primer中说:构造函数是一种特殊的成员函数。只要创建了类类型的新对象,就必须执行构造函数。构造函数的工作是确保每个对象的数据成员都有适当的初始值。 构造函数与其他函
2024-10-07
一篇文章了解常用Linux shell 命令的4 种使用方法

一篇文章了解常用Linux shell 命令的4 种使用方法

read命令 读取:的基本格式 读取[-rs] [-a ARRAY] [-d delim] [-n nchars] [-N nchars] [-p 提示符] [-t 超时] [var1 var2 var3.] -n(无换行符) -p(提示语句) -n(字符数) -t(
2024-10-07
String.valueOf(Object) 与String.valueOf(Object) 的比较对象.toString(对象)

String.valueOf(Object) 与String.valueOf(Object) 的比较对象.toString(对象)

String.valueOf(Object) 和Objects.toString(Object) 这两个方法本质上做同样的事情: 对传入的对象调用toString() 方法。在这种情况下,只要字符串“null”不为null,或者将null
2024-10-07
精通英语:直到

精通英语:直到

2.他们工作到八点钟。 3.她昨晚十点才睡觉。昨晚她直到十点才睡觉。 4.直到听到闹钟声我才醒来。 (点击) 直到它的同义词是直到。 作为准备。随后是n./n.phr。它的意思是直到
2024-10-07
初中英语考试70要求句型详解:till/until和not

初中英语考试70要求句型详解:till/until和not

4. not.until.句型中的强调句,无需倒置词序。 他发明了许多治疗伤员的新方法,但直到战后他才做出了最重要的发现。 5. not.until.句型与其他句型的比较 not.until. 句型总是表
2024-10-07
在强调句中使用not.until. 结构

在强调句中使用not.until. 结构

大家一定要熟悉强调句的使用,尤其是作为形式主语的强调句型。但当句子中出现not.until.时,很多朋友还是会感到困惑。今天我就和大家一起探讨一下not…until…强调句的用法。
2024-10-07
直到和不.直到的用法总结

直到和不.直到的用法总结

til的意思是“直到”,表示动作持续到某一时间点,常与进行性动词连用; not.until 的意思是“直到.”,表示该动作要到某个时间点才会发生,否则不会发生。它经常与瞬态动词一起使用
2024-10-07