对于上面的图解 所有的一切基于上图展开 下面每个部分的截图来自上图
前言
所谓的类加载全过程,也就是类加载机制,类加载机制是在创建对象的时候触发的,创建对象的过程如下面所示:
对象的创建过程一般是从 new 指令(JVM层面)开始:
(1)首先检查 new 指令的参数是否能在常量池中定位到一个类的符号引用;
(2)如果没有,说明该类还没有被加载,使用类加载机制进行类的加载(本文详细介绍的关于类加载机制的相关概念)
(3)如果有,虚拟机将在堆中为新生对象分配内存。分配内存方式有:指针碰撞和空闲列表,具体选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又是由所采用的垃圾收集器是否带有压缩整理功能决定。
Java 代码编写以及编译
在 IDE 里面编写相关的 Java 代码,经过了 Java 编译器(java c)命令之后,可以将在 IDE 里面书写的 .java 文件编译成为 .class 文件;
注意: 1、.class 文件是二进制文件,这个文件中包含字节码指令信息; 字节码指令是一个个的命令,两者不是同一个东西;
2、很多的博客讲到 .class 文件是字节码文件是不正确的表述,字节码是Java 虚拟机可以执行的一个个指令,被包含在了 .class 文件中;
.class 文件包含字节码指令;
类加载器介绍以及双亲委派模型
在 Java 类加载的全过程中,第一步就是加载过程,注意这个加载
过程和类加载的全过程
是不一样的;
这个加载
是基于双亲委派机制进而选择合适的类加载器完成的; 在这个加载
主要完成的任务有:
1、通过一个类的全限定名来获取定义这个类的二进制流;
2、将这个字节流代表的静态存储结构转换为方法区的运行时数据结构
3、在内存中生成一个代表这个类的 java.lang.Class 对象(这个类对象用来描述类信息)
,作为方法区里面的这个类的访问接口
上面的扩充解释:
1、获取二进制流的方式是多样化的,可以从数据库中获取,可以从网络中获取等
2、方法区方法的字节流的数据结构也不是确定的,可以根据具体的虚拟机的实现进行合理的数据结构的设计
3、当数据被合理的数据结构安排好之后,创建一个类对象,外部的程序访问这个类通过这个类对象接口进行访问;
类加载器分类双亲委派模型
也可以叫做双亲委派机制; 当一个类加载器收到加载一个类的请求的时候,该加载器不会直接去加载这个类,而是将这个请求委托到自己的父加载器尝试加载,层层的委托一直到最高一层的加载器:启动类加载器;当启动类加载器也无法加载的时候,才会层层向下传递,让子类加载器尝试进行类的加载;
不同类型的类加载器主要加载的内容:启动类加载器:Java JRE 运行时的核心类库文件负责加载%JAVA_HOME%/jre/lib下的jar包(以及由虚拟机参数 -Xbootclasspath 指定的类)扩展类加载器:Java JRE 运行时候的工具类库文件负责加载%JAVA_HOME%/jre/ext或者java.ext.dirs系统熟悉指定的目录的jar包,可以将自己写的工具包放到这个目录下,可以方便自己使用应用程序类加载器:负责加载加载来自java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类路径用户自定义类加载器,用户根据需要自己重写了 loadClass() 或者 findClass() 方法实现了自定义类加载器
双亲委派机制下的类加载器的优点
1、保证了安全性,JDK 自身携带的相关类被加载之后,和 JDK 同名字的类是不会被加载的,除非到 JDK 安装的位置替换到 JDK 代码,保证了程序的安全性了;开发人员写的类在服务器上加载,不会被其他人写的类加载替换掉,保证了程序运行的安全性;
2、避免了一个类的重复加载,判断出来一个类已经被加载了,那么后续就不会再次被加载了;
双亲委派机制也是可以破坏的
1、用户可能会书写一些代码在loadClass 中,这个时候可能会破坏双亲委派机制;双亲委派的实现主要是在loadClass 这个方法中实现的,重写了这个类就会破坏双亲委派机制,但是一般建议重写 findClass 这样可以保证自己的类加载逻辑,并且不会破坏双亲委派机制
2、双亲委派机制自身的设计缺陷,为了弥补也是会破坏类加载机制的,基础类型调用用户代码的时候,使用率双亲委托机制可能导致用户的代码无法被加载执行,此时就得需要破坏双亲委派机制;
类加载全过程
在经历了上面的基于双请委派模型的类加载器加载之后(这个阶段选择了适合当前类的一个类加载器)
,之后类加载全过程继续进行;
在类加载的全过程中,会经历 加载 -- 验证 -- 准备 -- 解析 -- 初始化
在上图中,蓝色的验证,准备,解析三个小的步骤可以合起来叫做连接(Lingking)
加载在上面已近进行了阐述,下面主要是验证等阶段:
验证
验证是连接阶段的第一步,确保加载进来的 .calss 文件字节流包含的信息符合《Java虚拟机规范》的全部约束要求,保证加载进来的 .class 文件里面的信息对于 JVM 没有损害;
验证有:文件格式验证、元数据验证、字节码验证、符号引用验证
准备
为类中的变量赋予初始值,也就是对于类中的静态变量赋予初始值; 这些变量使用的内存是在方法区中分配的,方法区中在 JDK 8 之后只是一个逻辑方面的区域,不是实际存在的区域;
注意:这里在准备阶段只是对静态变量赋予 0 值,实例变量在没有赋予值的;并且通常情况下面赋予的是 0 值,但是在特殊情况下面赋予的是类中定义的值;
普通情况:public static int value = 10; 初始赋予是 0 ;特殊情况:public static final int value = 123; 赋值就是 123
解析
符号引用替换为直接引用的过程;
初始化
这是类加载的最后一个步骤,将准备阶段的 0 值赋予类中给定的值;
换句话说,初始化阶段就是执行类构造器 () 方法的过程;
类的生命周期
一个类从 .class 文件 加载到虚拟机中并且从虚拟机中卸载经历了下面的全部过程;
加载--验证 -- 准备 -- 解析 -- 初始化 -- 使用 -- 卸载 类加载全过程到了初始化就全部结束了,下面就是对象的使用以及类的卸载,整个的类加载以及对象的使用到类的卸载这样一个完整的过程叫做类的生命周期;
类加载器与类加载机制
类加载机制中的加载
第一个操作:加载
操作包含了类加载器;换句话说,这一步的加载需要选择合适的类加载器,而选择合适类加载器使用到了基于双亲委派机制
选择的类加载器;
这是两个不同层面的含义;
小结
本文介绍了Java 类加载机制,介绍了类加载机制中类加载器,以及加载类是使用双亲委派机制选择合适的类加载器加载类,类的生命周期,对象相关的概念进行了详细的阐述,对于容易混淆的概念也进行了简单的辨析,希望大家可以对于这一块知识有更加准备的理解;
参考
周志明 Java虚拟机 第三版
原文:https://juejin.cn/post/7096302669697908766