这就是虚拟机的类加载。 类加载概念 ? 上图是类加载和卸载的整个过程示意图,其中验证、准备、解析统称为连接阶段。 修饰、在编译期就把结果存入常量池的静态字段除外))、putstatic(设置一个静态字段)或invokestatic(调用静态方法)这4条指令时,如果类没有进行初始化,则必须先触发其初始化。 类加载-加载阶段 加载是类加载的第一个阶段,加载阶段的主要目标是: 通过一个类的全限定名来获取定义此类的二进制字节流 将字节流的静态信息结构转换为方法区(元数据区)的运行时数据结构 在内存中生成一个代表这个类的 非数组类的加载需要通过类加载器实现,既可以使用系统的提供的引导类加载,也可以使用用户自定义的类加载器去完成,关于类加载器后续我会单独写一篇文章来介绍,并且实现我们自己的一个类加载器。 本期类加载的加载阶段就介绍到这,下期我们会讲解类加载的连接阶段,我们下期再见!!!
验证阶段大致会完成4个阶段的检验动作: 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型 加载机制 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入 双亲委托,先让父类加载器试图加载该类, ,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求(它管理的范围之中没有这个类)时,子加载器才会尝试着自己去加载 ? 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。 自定义加载器 既然JVM已经提供了默认的类加载器,为什么还要定义自已的类加载器呢?
文章目录 一、类加载器 二、类加载的双亲委托机制 一、类加载器 ---- Java 虚拟机 ClassLoader 类加载器 : Bootstrap ClassLoader : 启动类加载器 , 该 加载器由 ; 类加载的双亲委托机制 : 类加载器 ClassLoader 接收到 类加载任务 之后 , 自己不会先进行加载 , 反而将该 类加载任务 委托 给 父类类加载器 执行 ; 父类类加载器 接收 到 该 类加载任务 之后 , 也会 委托 父类的父类 类加载器 执行 ; 委托操作 , 会一直传递到 最顶层的 启动类加载器 Bootstrap ClassLoader ; 如果 启动类加载器 Bootstrap ClassLoader 完成了 类加载 操作 , 返回加载的类 ; 如果 启动类加载器 Bootstrap ClassLoader 无法完成 类加载 操作 , 就会 将 类加载任务 委托给 子类完成 ; 同理 , 父类 委托 给子类的 类加载任务 , 如果 子类类加载器 可以完成加载 , 成功返回 , 如果子类类加载器无法完成加载 , 就再次 将 类加载任务 委托给 子类的子类 , 继续向下传递 ;
当类也就是class文件被加载到虚拟机内存开始,到卸载出内存为止。它将要执行以下7个步骤: 加载 验证 准备 解析 初始化 使用 卸载 ? 下面我们看一下每一个阶段的详细流程。 加载 加载也就是把class文件加载到虚拟机内存中。在加载时虚拟机需要完成3个方面的工作: 通过一个类的全限定名来获取类的二进制字节流。 将字节流所代表的静态存储结构转化为方法区中的运行时数据结构。 在验证阶段虚拟机主要分为4个步骤来验证: 文件格式验证:主要的验证是验证二进制流是否符合Class文件的规范,并且是否能被当前版本的虚拟机处理。 字节码验证:这个阶段是最复杂的也是最耗时的验证,它主要目的是对类的方法体进行验证,校验类中的方法在运行时,不会做出威胁虚拟机的安全事件。 在解析时主要分为下面的几种: 类或接口的解析 字段解析 类方法解析 接口方法解析 初始化 初始化阶段主要的目的是为静态变量或者静态代码块执行赋值的操作。
类加载器种类 启动类加载器,Bootstrap ClassLoader,加载JACA_HOME\lib,或者被-Xbootclasspath参数限定的类 扩展类加载器,Extension ClassLoader ,加载\lib\ext,或者被java.ext.dirs系统变量指定的类 应用程序类加载器,Application ClassLoader,加载ClassPath中的类库 自定义类加载器,通过继承ClassLoader 实现,一般是加载我们的自定义类 双亲委派模型 类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的;除了启动类加载器,每个类都有其父类加载器(父子关系由组合(不是继承)来实现); 所谓双亲委派是指每次收到类加载请求时,先将请求委派给父类加载器完成(所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载(该加载器的搜索范围中没有找到对应的类 双亲委派好处 避免同一个类被多次加载; 每个加载器只能加载自己范围内的类; 类加载过程 类加载分为三个步骤:加载,连接,初始化; ?
文章目录一、什么是类加载器、类加载器作用1.1 定义与作用1.2 应用场景二、类加载时机三、类加载的完整过程3.1 加载3.2 验证3.3 准备3.4 解析3.5 初始化3.6 使用3.7 小节四、类加载的分类 【理解】4.1 概述4.2 JDK8及之前的版本4.2.1 启动类加载器4.2.2 扩展类加载器和应用程序类加载器扩展类加载器应用程序类加载器4.3 JDK9之后的类加载器4.4 ClassLoader $AppClassLoader@18b4aac2 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); JDK9及之后扩展类加载器(Extension ClassLoader)变成了平台类加载器(Platform ClassLoader)(4)什么是双亲委派机制每个Java实现的类加载器中保留了一个成员变量叫 ,扩展类加载器的父类加载器是启动类加载器。
类加载器子系统作用 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。 于是我就用 CAFEDEAD 作为对象持久化文件的魔数, 当然,这两个魔数有着共同的前缀: 4个十六进制字符(CAFE, Java和咖啡有一段深沉的虐恋), 我选中了BABE(宝贝) 加载的类信息存放于一块称为方法区的内存空间。 > 5 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V> 8 return 用流程图表示上述示例代码: 加载阶段 由于Java采用的是懒加载策略,只有当我们需要用到这个类的时候才会去加载他 初始化阶段就是执行类构造器方法<clinit>()的过程。
为什么要自定义加载器 原因: 1、存放在自定义路径上的类,需要通过自定义类加载器去加载。 【注意:AppClassLoader加载classpath下的类】 2、类不一定从文件中加载,也可能从网络中的流中加载,这就需要自定义加载器去实现加密解密。 3、可以定义类的实现机制,实现类的热部署, 如OSGi中的bundle模块就是通过实现自己的ClassLoader实现的, 如tomcat实现的自定义类加载模型。 方法来加载类,获取class对象。 一直提交给启动类加载器去加载,通过他加载,加载到的永远是/lib下面的java.lang.String 3、在这个自己写的类中写上main方法 public static void main(String
\log4j\1.2.17\log4j-1.2.17.jar;D:\develop\apache-maven-3.6.1\repository\org\projectlombok\lombok\1.16.18 \log4j\1.2.17\log4j-1.2.17.jar;D:\develop\apache-maven-3.6.1\repository\org\projectlombok\lombok\1.16.18 1 //Launcher的构造方法 2 public Launcher() { 3 Launcher.ExtClassLoader var1; 4 try { 5 //构造扩展类加载器,在构造的过程中将其父加载器设置为 比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载 器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天 没找到Math类,则向下退回加载 次,保证被加载类的唯一性 看一个类加载示例: 1package java.lang; 2 3 public class String { 4 public static void main(String
(4)解析方法,parse_methods()。 (5)创建与Java类对等的内部对象instanceKlass,new_instanceKlass()。 Java程序主类的类加载器默认是系统加载器,该加载器是JDK类库中定义的sun.misc.AppClassLoader,关于该加载器的细节会在后文详述。JVM体系中加载器的继承关系如下图所示。 ? (4)SystemDictionary::resolve_or_fail()接口经过一系列调用,最终调用SystemDictionary:: resolve_instance_class_or_null 该接口的第2个入参(入参从位置1开始计数)指明所调用的Java类对应的instance,第4个入参指明所调用的特定方法,第5个入参指明所调用的Java类的签名信息。 当JVM执行Java程序主类加载时,向JavaCalls::call_virtual()接口传入的第2和第4个入参分别是class_loader和vmSymbols::loadClass_name(),
文章目录 一、类加载时机 1、隐式加载 2、显式加载 二、类加载步骤 一、类加载时机 ---- 1、隐式加载 类加载 的 隐式加载 , 没有明确的说明加载某个类 , 但是进行了如下操作 : 使用 new 关键字 直接 创建类的实例对象 ; 使用 new 关键字 直接 创建类的 子类 实例对象 ; 通过 反射方式 创建 类 / 子类 实例对象 ; 访问 类的 静态变量 , 对静态变量 进行 读 或者 写 操作 都会触发 隐式加载 ; 访问 类的 静态函数 ; 2、显式加载 显式加载 : 明确的说明要加载某个类 , 使用 Class.forName() 加载指定的类 ; 使用 ClassLoader.loadClass 加载指令的类 ; 二、类加载步骤 ---- 参考 【Java 虚拟机原理】Java 类加载过程 ( 加载 | 连接 - 验证 准备 解析 | 初始化 | 使用 | 卸载 ) 博客 ; 类加载步骤 : ① 装载 : 加载某个类时 , 首先要 查找 并 导入 Class 字节码文件 ; ② 链接 : 该阶段 , 可以分为以下 3 个子阶段 ; 验证 : 验证字节码文件是否正确 ; 准备 :
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM 规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了 .class 文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误 ,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器 在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。 4. 自定义类加载器(User ClassLoader), 如果有必要,我们还可以加入自定义的类加载器。 既然是加载这个类时出现的问题,我们首先要知道是哪个类加载器在尝试加载这个类。 结合类加载机制中的全盘负责机制,我们知道这个匿名类是由加载依赖它的 org.apache.iotdb.MySum 的类加载器来加载的,我们把这个类加载器记为 A。
类的生命周期 类加载过程 加载 验证 准备 解析 初始化 卸载 类的生命周期 一个类的完整生命周期如下: 类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 loadClass() 方法)。 4 条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。 该类没有在其他任何地方被引用 该类的类加载器的实例已被 GC 所以,在 JVM 生命周期内,由 jvm 自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。 而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。 参考
3,反射调用的时候 4,main方法所在的类,最先初始化。 类加载器只用于实现类的加载动作。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。 从开发人员的角度类加载器有三种:启动类的加载器,扩展加载器,应用程序类加载器 启动类加载器:负责将存放在<JAVA_HOME>\lib目录中的类库加载到虚拟机内存中 扩展加载器:负责将存放在<JAVA_HOME >\lib\ext目录中的类库加载到虚拟机内存中 应用程序类加载器:它负责加载用户类路径(ClassPath)上所指定的类库 双亲委派机制 类加载器之间如下图的这种层次关系,称为类加载器的双亲委派模型。 双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中
本节来学习类加载机制 什么是类加载 简单来说就是将.class文件实例成Class对象并进行初始化的过程,字节码必须通过类加载过程加载到JVM环境后才可以执行 classloder(类加载器)的使命就是将 .class文件加载到内存中,在启动之初进行类的Load、Link和Init,在加载类时使用的是双亲委派模型 类加载 类加载过程 1 加载阶段读取类文件产生的二进制流并转为特定的数据结构,创建对应的 类加载器 字节码文件加载到内存就是通过类加载器来实现的,类加载器类似于原始部落结构存在权力等级,最高的一层是家族中威望最高的Bootstrap,它是jvm启动时创建的,是通过C来实现的,并不存在于JVM 中 双亲委派模型 类加载器之间的这种层次关系就是双亲委派模型。 双亲委派模型要求除了顶层的启动类加载器外,其他的类加载器都应有自己的父类加载器 这部分内容是对第一张图中加载这个方框部分的内容的拓展
用户可以自己定义类加载器来加载类。 逻辑是从自定义加载器一直往上父类加载器递归调用,直到扩展类加载器发现没有parent了,就会去查看引导类加载器加没加载过该类。 加载顺序应该是先查看引导类加载器是否加载,再由父类加载器尝试加载,最后是子类加载器自己加载。 双亲委派:向上委派过程:第一步: 将自定义加载器挂载到应用程序类加载器;第二步: 应用程序类加载器将请求委托给扩展类加载器;第三步: 扩展类加载器将请求委托给启动类加载器。 向下委派的加载过程(报ClassNotFund的过程):第一步: 启动类加载器在加载路径下查找并加载Class文件,如果没有找到就交给扩展类加载器加载 第二步:扩展类加载器在它的加载路径下查找并加载Class
类的生命周期 加载->连接->初始化->使用->卸载 其中连接包括:验证->准备->解析 类加载过程 Class文件需要加载到虚拟机之后才能运行。 系统加载Class文件的步骤:加载->连接->初始化。 加载 1.通过全类名加载该类对应的二进制字节流。 2.将该二进制字节流代表的静态存储结构存到方法区的运行时数据结构中。 一个非数组类的加载阶段可控性最强,可以在这个阶段自定义类加载器去控制字节流的获取方法(重写类加载器的classLoad()方法)。数组类型不通过类加载器创建,他直接由JVM创建。 4.当虚拟机启动时,用户需要定义一个启动类(main方法),虚拟机会先初始化含有main方法的类。 3.加载该类的类加载器被GC。 参考:JavaGuide
类加载器基本概念 顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。 类加载器的代理模式 类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。 加载类的过程 在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。 也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。 类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。
1.访问类的编译期静态常量时,不会触发类的初始化行为。类的初始化行为是指在类被加载之后(也就是类的Class对象被创建之后),为类的静态成员变量分配存储空间。 2.编译期静态常量会在编译阶段被存储到NonInitialization类的常量池中,在以后对编译期静态常量的引用都实际上被替换为对NonInitializaion类对自身常量池的引用,所以访问类的编译期静态常量并不会触发类的初始化行为 3.初始化一个类之前,会先初始化该类的父类。
例举了 5 个来源 (1)本地磁盘 (2)网上加载.class 文件(Applet) (3)从数据库中 (4)压缩文件中(ZAR,jar 等) (5)从其他文件生成的(JSP 应用) (4)符号引用验证:它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候。主要是对类自身以外的信息进行校验。目的是确保解析动作能够完成。 4、解析 解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程。什么是符号应用和直接引用呢? Bootstrap ClassLoader > Extention ClassLoader >AppclassLoader 一张图来看一下他们的层次关系 3.png 代码验证一下: 4.png 从上面的结果可以看出 ** 4、自定义类加载器 在这一部分第一小节中,我们提到了 java 系统为我们提供的三种类加载器,还给出了他们的层次关系图,最下面就是自定义类加载器,那么我们如何自己定义 类加载器呢?