JVM 类加载机制

下面文章是转载的,转载地址:JVM 类加载机制_大家好我是Boger的博客-CSDN博客_jvm类加载机制

转载文章是为了后期更好的学习,同时我也很希望其他人能专注其博客,如有知识产权的相关问题,请及时通知我,谢谢!

java文化通过javac编译成字节码文件,然后由JVM加载字节码。
运行时,解释器将字节码解释为一行行的机器码来执行。在此过程中,JIT(即时编译器)会将该部分字节码编译成机器码以获得更高的执行效率。

 


类加载:把一份被javac编译过的class文本文件通过加载,生成某种形式的Class数据结构进入内存,程序可以调用这个数据结构来构造出object

 

加载
加载是一个读取Class文件,将其转化为某种静态数据结构存储在方法区中,并在堆中生成一个便于用户调用的Class类型对象的过程。

连接
类加载之后虽然此时方法区内已经存在了该class的静态结构,堆中也存在了该class类型的对象,但是这并不代表JVM已经完全认可了这个类。如果程序想要使用这个类,就必须进行类的连接。

验证
这是连接中的第一步。
验证阶段:对class静态结构进行语法和语义上的分析保证不会产生危害JVM的行为。
验证中包含元数据验证、字节码验证、文件格式验证、符号引用验证(在解析阶段内发生)。
验证的内容也是会不断发展的,除了上面提到的四种验证之外,验证步骤其实已经不断加入了各种机制。

准备
连接中的第二步。
准备阶段:为类中定义的静态变量分配空间。
注意要知道分配空间和赋值是两个步骤。
对于final类型的基本类型静态变量以及字符串常量,在编译阶段就知道其值,那么在准备阶段就完成变量的空间分配以及赋值
对于非final类的静态变量,在准备阶段为变量分配空间且赋值为0,在初始化阶段再进行变量的赋值
对于final类型的引用类型变量,赋值也是在初始化阶段才进行

附:JDK8以前和JDK8及以后内存结构的变化

 

 

解析
连接中的第三步。
解析阶段:将符号引用替换为直接引用。
符号引用:当类A被编程成Class之后,假设类A中引用了类B,在编译阶段A不知道B有没有被编译且此时B也没有被加载,那么A此时不知道B的实际地址,所以使用一个字符串来代表B的地址。这个字符串就是符号引用。
直接引用:在运行当中,如果A发生了类加载,在解析阶段发现B还未发生类加载,那么会触发B的类加载,将符号引用替换为B的实际地址,这被称为直接引用。

多态中通过后期绑定来实现多态,而后期绑定就是通过动态解析来实现的。
如果A调用的B是一个具体的实现类,那么就称为静态解析。
如果A是一个抽象类或者接口,且有两个具体的实现类C和D,此时A的具体实现并不明确,无法确定使用哪个类的直接引用来进行替换符号引用。那这时候便会等到运行过程中发生了调用,此时虚拟机调用栈中会得到具体的类型信息,这时候再进行解析,这时候就能使用直接引用来替换符号引用。底层在这里对应了invokevirtual字节码指令。

初始化
这个阶段会执行代码中主动的资源初始化动作。这里的资源初始化动作是指成员变量的赋值动作、静态变量的赋值动作、静态代码块的逻辑,而不是指类的构造方法,只有显式地调用new指令,才会调用构造方法进行对象的实例化。

 
以下是JVM中双亲委派、类加载器的笔记,不过这部分我没做过多的笔记。
视频地址:https://www.bilibili.com/video/BV1X5411K7cw?spm_id_from=333.999.0.0

 

JVM内置三个重要的ClassLoader,除了启动类加载器之外的加载器都是用Java实现的:

BootstrapClassLoader(启动类加载器):最顶层加载类,由C++实现,负责加载JAVA_HOME下的lib目录下的jar包和类,并不是继承自java.lang.ClassLoader
ExtensionClassLoader(扩展类加载器):主要负责加载JAVA_HOME下的lib/ext目录下的jar包和类,继承自java.lang.ClassLoader
APPClassLoader(应用程序类加载器):面向用户的加载器,负责加载当前应用classpath下的所有jar包和类,继承自java.lang.ClassLoader

在这里插入图片描述

 

双亲委派:
当一个类加载器收到类加载请求时,会先判断当前类是否已经被加载过。如果当前类没有被加载过,当前类加载器也不会首先自己去加载,而是传递给自己的父亲加载器。只有父亲加载器无法加载,儿子加载器才会尝试自己去加载。因此所有的请求最终都会传送到顶层的启动加载器中。
当父亲加载器是null时,会使用启动类加载器作为父类加载器。
什么时候无法加载呢?根据类的限定名,类加载器没有在自己负责的加载路径中找到该类。
注意这里说的是父亲加载器和儿子加载器,而不是说父加载器和子加载器,因为上面图中这些箭头并非表示继承关系而是一种逻辑关系,使用组合实现(类加载器类中的parent变量表示当前加载器的父亲加载器)。

在这里插入图片描述
双亲委派模型的好处(引用自JavaGuide):
双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

 

沙箱安全机制:
自定义String类时:在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java.lang.String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。
这样可以保证对java核心源代码的保护,这就是沙箱安全机制。

如何判断两个class对象是否相同?
在JVM中表示两个class对象是否为同一个类存在两个必要条件:

类的完整类名必须一致,包括包名
加载这个类的ClassLoader(指ClassLoader实例对象)必须相同
换句话说,在JVM中,即使这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的

对类加载器的引用
JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的
如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中
当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的

 
补充:ClassNotFoundException 和 NoClassDefFoundError 的区别

先看以下Exception和Error的区别:

①Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。
既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。错误(error): 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。

②Exception 是程序正常运行中,可以预料的意外情况,并且可能应该被捕获,进行相应处理。
Exception 又分为可检查(checked)异常和不检查(unchecked)异常【即运行时异常】,可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分:

①检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
②运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略,类似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
举例形象描述如下
假如你开车上山:
①车坏了,你拿出工具箱修一修,修好继续上路(Exception被捕获,从异常中恢复,继续程序的运行)
②车坏了,你不知道怎么修,打电话告诉修车行,告诉你是什么问题,要车行过来修。(在当前的逻辑背景下,你不知道是怎么样的处理逻辑,把异常抛出去到更高的业务层来处理)。
③你打电话的时候,要尽量具体,不能只说我车动不了了。那修车行很难定位你的问题。(要捕获特定的异常,不能捕获类似Exception的通用异常)。
④你开车上山,山塌了,这你还能修吗?(Error:导致你的运行环境进入不正常的状态,很难恢复)

接下来再来看ClassNotFoundExeption和NoClassDefFoundError:

ClassNotFoundException:
从类继承层次上来看,ClassNotFoundException是从Exception继承的,ClassNotFoundException是一个检查性异常。
当应用程序运行的过程中尝试使用类加载器去加载Class文件的时候,如果没有在classpath中查找到指定的类,就会抛出ClassNotFoundException。一般情况下,当我们使用Class.forName()或者ClassLoader.loadClass以及使用ClassLoader.findSystemClass()在运行时加载类的时候,如果类没有被找到,那么就会导致JVM抛出ClassNotFoundException。

NoClassDefFoundError:
从类继承层次上看,NoClassDefFoundError是从Error继承的。和ClassNotFoundException相比,明显的一个区别是,NoClassDefFoundError并不需要应用程序去关心catch的问题。
当JVM在加载一个类的时候,如果这个类在编译时是可用的,但是在运行时找不到这个类的定义的时候,JVM就会抛出一个NoClassDefFoundError错误。比如当我们在new一个类的实例的时候,如果在运行时类找不到,则会抛出一个NoClassDefFoundError的错误。
举个例子,先创建一个TempClass,然后编译以后,将TempClass生产的TempClass.class文件删除,然后执行程序,就会输出NoClassDefFoundError。
————————————————
版权声明:本文为CSDN博主「大家好我是Boger」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/QQ1149646297/article/details/123308917

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值