前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java虚拟机如何加载一个类

java虚拟机如何加载一个类

作者头像
小土豆Yuki
发布2021-01-18 11:35:08
3010
发布2021-01-18 11:35:08
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗

现实生活中,我们经常要盖房子,那么盖房子要经过哪些过程呢,首先我们要请建筑师出个方案,然后去市政部门进行报备,验证,通过后才可以盖房子,改好之后还要进行装修,之后才能住人,

其实我们发现盖房子就和java虚拟机中的类加载还是挺像的,从class文件到内存中类,按先后顺序需要加载,链接以及初始化三大步骤,其中,链接过程同样需要验证,而内存中的类没有经过初始化,同样不能使用.接下来我们就详细的说一下每个步骤具体都干些什么

加载

加载就是指查找字节流,并且根据这个创建类的过程,而Java虚拟机是根据类加载器来查找字节流的过程.

我们还以盖房子来说,我要盖的房子就是类,而我们请的建筑师就是我们的类加载器,而现实有许多建筑师,他们都有共同的祖师爷,就相当我们的Java虚拟机中的启动类加载器,启动类加载器是有c++实现。

除了启动类加载器外,还有其他类加载器,且都是java.lang.ClassLoader的子类,这个类加载器需要另外一个加载器加载到java虚拟机,比如启动类加载器,才能执行类加载器

此时我们以建筑师来说,假设他有一个规则,就是不能独自接活,必须由他们的师傅过目,如果师傅看不上的或才能交给他们进行处理,正如我们的Java虚拟机一样,每当一个类加载器接受到加载请求时候,他会先将请求转发给父类加载器,在父类加载器没有找到所请求的类的情况下,该类加载器才能进行加载,这就叫做双亲委派机制,

Java9之前,启动类加载器就是加载比较重要的类,比如存放在lib目录下jar包中的类或有虚拟机参数-Xbootclasspath指定的类,除了启动类加载器,还有另外重要的类加载器,扩展类加载器和应用类加载器,

扩展类加载器,加载一些相对次要的类,但有通用的类,比如存放在lib/ext下的类或有系统变量java.ext.dirs指定的类.

应用类加载器的父类加载器就是扩展类加载器,他负责加载应用程序路径下的类(即虚拟机参数-cp/-classpath,系统变量java.class.path,环境变量CLASSPATH所指定的路径),默认情况下,应用程序包含的类便是由应用类加载器加载的。

java9引入了模块系统,且略微的更改上述的类加载,如扩展类加载器更名为平台类加载器,java se中除了少数几个模块(少数是指比如java.base是由启动类加载器加载),其他模块均由平台类加载器加载,

Java除了提供核心的类加载器,还提供了自定义类加载器,来实现特殊的加载方式,举例来说,我们可以对class文件进行加密,加载时再利用自定义加载器进行解密.

类加载器还提供了另外的功能,命名空间的作用,比如显示生活中,建筑师建造的房子表上自己的名字就是自己的作品,而java虚拟机中,类的唯一性是有类加载器和全类名一同确认的,即便是同一串字节流,经由不同的类加载器加载,就是不同的类,在大型应用中,我们往往借助这个特性来运行同一个类的不通版本。

链接

链接的是指创建的类合并至java虚拟机,使他能够执行,他也分为三个步骤,验证,准备,解析

验证的目的就是确保被加载类能够满足java虚拟机的约束条件,正如我们盖房子,设计的房子要交给市政部分审核,只有审核通过,才能继续建造工作

准备的目的为了加载类的静态字段分配内存,除了分配内存,部分java虚拟机还会在此阶段构造其他类层次相关的数据结构,比如用来实现虚方法的动态绑定的方法表。此时就算改好了毛坯房

在class文件被加载到java虚拟机之前,这个类无法知道其他类及其方法,字段以及具体地址,甚至自己的方法,字段的资质,因此在每当需要应用这些成员时候,java编译器会生成一个符号引用,在运行阶段,这个符号引用一般都能够无歧义的定位到具体的目标。

对于一个方法的调用,编辑器会生成其目标方法所在类的名字,目标方法的名字,接受参数类型以及返回值类型的符号引用,来指代所要调用的方法。

解析就是把符号引用解析成实际的应用,如果符号引用指向一个未加载的类,或者为被加载的字段或方法,那么解析将触发这个类的加载(但未必触发这个链接以及初始化)

符号引用可以理解为某个房子,但是他并一定存在,而把房子的实际地址就是实际的引用,正如我们想要到某个房子,就必须先把房子盖起来,就相当于要加载类

其中我们要注意的是解析未必要在链接中完成,java虚拟机仅仅规定了,如果某些字节码使用了符号应用,那么执行这个字节码之前,要完成这个符号引用的解析

初始化

在java代码中,如果要初始化一个静态子弹,我们可以直接复制,也可以直接在静态代码块中赋值,

但是如果我们给静态变量被final修饰,且是基本类型或字符串的时候,那么该子弹便会被Java编译器标记成常量值,其初始化直接由java虚拟机完成,除此之外的情况以及所有静态代码块的代码块,则会被java编译器放到同一个方法中,并命名为clinit

初始化是类加载的最后一步,便是标记常量字段复制,以及执行clinit方法的过程,java虚拟机会使用锁确保clinit执行一次

初始化完了之后,才能正式的成为可执行的状态,比如我们的房子装修完之后才能真正的主人

最后,类的初始化何时会被触发,JVM规范枚举了下面多种触发情况

  • 当虚拟机启动时,初始化用户指定的类
  • 当遇到用以新建目标实例new指令的时候,初始化new指令的目标类
  • 当遇到静态方法指令时候,初始化静态方法所在的类
  • 当遇到访问静态字段的指令时候,初始化静态字段所在的类
  • 子类的初始化会触发父类的初始化
  • 如果一个接口定义了default方法,那么直接实现或间接实现接口的类的初始化,会触发该接口的初始化
  • 使用反射API对某个类进行反射调用,初始化这个类
  • 当初次调用MethondHandle实例时,初始化该MethondHandle指向的方法坐在的类
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档