java类加载
1.类加载
(1)如何区分一个文件的文件的类型?
错误方法:通过扩展名
正确解法:靠魔数(头4个字节)来区分当前的文件
.class文件的魔数是OxCAFEBABY
(2)什么是类加载?
类加载机制是指虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
2.类加载的生命周期
类加载的生命周期分为五部分,分别为加载、连接、初始化、使用和卸载。连接又分为三个阶段:验证、准备、解析。
特别说明
Java中类型的加载、连接、初始化过程都是在程序运行期间完成的,这种方法会使类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性。
发生顺序如图所示:
这五个阶段的顺序是确定的,而且只能按照顺序有序开始,但解析阶段不一定,它在某些情况下可以在初始化阶段之后才开始,这是为了支持Java语言运行时的绑定(也叫动态绑定/晚期绑定)。这里需要特别说明的是,只是按顺序“开始”,不一定要等上一个阶段完成后才开始,这些阶段可以互相交叉地混合式进行,通常会在一个阶段执行的过程中调用和激活另外一个阶段。
类加载的时机
(1)什么情况下会开始类加载过程的第一个阶段?
主动引用
a.遇到new/getStatic/putStatic/invokeStatic这个4条字节码指令,如果类没有进行初始化,则会先触发类的初始化。
b.反射,使用java.lang.reflect包的方法对类进行反射调用的时候,如果类如果没有进化过初始化,则需求先触发其初始化。
c.类存在继承关系,如果初始化一个类,发现父类没有被初始化,触发父类的初始化
d.虚拟机启动之后,都会有一个要执行的主类,虚拟机触发主类的初始化
e.java.lang.invoke.MethodHandle实例时候,会所对应的类进行初始化
*除了以上五种引用,其他的引用都不会触发初始化
被动引用
a.通过子类引用父类的静态字段,子类是不会被初始化的
b.创建一个自定义类型的数据,不会触发该类的初始化
c.常量在编译阶段会直接放入常量池,并没有引用定义常量池的类,所以不会触发该类的初始化
(2)接口加载和类加载过程一样吗?
不完全一样
接口也有初始化过程,区别在于主动引用中的第c中:当一个类初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。
类加载的过程
加载
在加载阶段,JVM都做了什么事情?
a. 通过类的全限定名来获取定义类的二进制字节流
b. 二进制字节流所代表的静态结构转化为方法区运行时的动态的数据结构
c. 在内存中生成一个代表该类的class对象(唯一性),可以作为访问方法区的入口
连接
连接分为三个阶段,验证、准备、解析
验证
检查载入的class文件数据正确性
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
(1).类变量会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到Java堆中
(2).初始值指的是数据类型初始值,而不是代码中被显示赋予的值
例:
public static int value =1
//这里准备阶段过后的值为0,而不是1 ,赋值为1的阶段在初始化阶段
注意:value被static修饰的准备阶段之后是0 ,但是如果同时被final 和static修饰准备阶段之后就是1了,这里可以理解为static final 在编译期就将结果放入调用它的常量池中了
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用
直接引用
初始化
初始化阶段,java程序代码才开始真正执行,在准备阶段已经为类变量赋过一次值(赋的值是默数据类型默认值),在这个阶段是可以根据程序员自己的需求来赋值。
初始化阶段,主要为类的静态变量赋予正确的初始值,JVM负责对对类进行初始化,主要对类变量进行初始化。
Java中对类变量设置初始值的两种方式
1.声明类变量为指定初始值
2.使用静态代码块为类变量指定初始值
JVM初始化步骤
a.如果这个类还没有被加载或连接,则程序先加载并连接该类
b.如果该类的直接父类还没有初始化,则先初始化其直接父类
c.如果类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机
只有当类的主动使用时
类的主动使用
a.创建类的实例,也就是new的方式
b.访问某个类或者接口的静态变量,或者对该静态变量赋值
c.调用类的静态方法
d.反射
e.初始化某个类的子类,则其父类也会被初始化
f.Java虚拟机启动时被标明为启动类的类(JavaTest),直接使用java.exe命令来运行某个主类
类加载器
Bootstrap ClassLoader(启动类加载器)
JAVA_HOME/jre/lib下的jar包
其下的jar包都是JVM运行时所必需的jar包
本身是一个类,本身也需要被加载到JVM上才能够去使用
如果某一个类的类加载器是BootstrapClassLoader,那么该类的getClass
Loader()方法返回null
ExtClassLoader(扩展类加载器)
JAVA_HOME/jre/ext下的所有jar包
AppClassLoader(应用类加载器)
主要加载的是开发者在应用程序中编写的类 CLASSPATH路径下的所有jar文件
双亲委派模型
实现双亲委派步骤
1.创建一个类继承ClassLoader抽象类
2.重写findClass()方法
3.在findClass()方法中调用defineClass()
双亲委派优点
1.可以避免重复加载,父类已经加载了,子类就不需要加载了
2.更加安全,很好的解决了各个类加载器的基础类的统一问题,如果不适用这种方式,那么用户就可以随意定义类加载器来加载核心apl,会带来相关隐患。
类加载的三个方法
loadClass
负责以双亲委派的方式去加载类
findClass
通过给定的类的全限定名去找该类的Class对象
defineClass
负责从class字节码文件中加载class对象
本文地址:https://blog.csdn.net/qq_43713384/article/details/109764019
下一篇: HashMap基本的实现流程之扩容