jvm-类加载过程(加载、验证、准备、解析)

类加载过程

在这里插入图片描述

加载

“加载”是“类加载”(Class Loading)过程的第一步。这个加载过程主要就是靠类加载器实现的,包括用户自定义类加载器。

加载过程

在加载的过程中,JVM主要做3件事情

  • 通过一个类的全限定名来获取定义此类的二进制字节流(class文件),在程序运行过程中,当要访问一个类时,若发现这个类尚未被加载,并满足类初始化的条件时,就根据要被初始化的这个类的全限定名找到该类的二进制字节流,开始加载过程。
  • 将这个字节流的静态存储结构转化为方法区的运行时数据结构
  • 在内存中创建一个该类的java.lang.Class对象,作为方法区该类的各种数据的访问入口
    程序在运行中所有对该类的访问都通过这个类对象,也就是这个Class对象是提供给外界访问该类的接 口。
加载源

JVM规范对于加载过程给予了较大的宽松度.一般二进制字节流都从已经编译好的本地class文件中读取,此外还可以从以下地方读取。

  • zip包:Jar、War、Ear等
  • 其它文件生成:由JSP文件中生成对应的Class类.
  • 数据库中:将二进制字节流存储至数据库中,然后在加载时从数据库中读取.有些中间件会这么做,来实现代码在集群间分发
  • 网络:从网络中获取二进制字节流.典型就是Applet.
  • 运行时计算生成:动态代理技术,用ProxyGenerator.generateProxyClass为特定接口生成形式为"*$Proxy"的代理类的二进制字节流
类和数组加载的区别

数组也有类型,称为“数组类型”.如:String[] str = new String[10];
这个数组的数组类型是 [Ljava.lang.String ,而String只是这个数组的元素类型。
数组类和非数组类的类加载是不同的,具体情况如下:

  • 非数组类:是由类加载器来完成。
  • 数组类:数组类本身不通过类加载器创建,它是由java虚拟机直接创建,但数组类与类加载器有很密切的关系,因为数组类的元素类型最终要靠类加载器创建。
加载过程的注意点
  • JVM规范并未给出类在方法区中存放的数据结构
    • 类完成加载后,二进制字节流就以特定的数据结构存储在方法区中,但存储的数据结构是由虚拟机自己定义的,虚拟机规范并没有指定。
  • JVM规范并没有指定Class对象存放的位置
    • 在二进制字节流以特定格式存储在方法区后,JVM会创建一个java.lang.Class类的对象,作为本类的外部访问接口。
    • 既然是对象就应该存放在Java堆中,不过JVM规范并没有给出限制,不同的虚拟机根据自己的需求存放这个对象。
    • HotSpot将Class对象存放在方法区。
  • 加载阶段和链接阶段是交叉的
    • 类加载的过程中每个步骤的开始顺序都有严格限制,但每个步骤的结束顺序没有限制。也就是说,类加载过程中,必须按照如下顺序开始:加载 -> 链接 -> 初始化。但结束顺序无所谓,因此由于每个步骤处理时间的长短不一就会导致有些步骤会出现交叉。
验证

验证阶段比较耗时,它非常重要但不一定必要(因为对程序运行期没有影响),如果所运行的代码已经被反复使用和验证过,那么可以使用 -Xverify:none 参数关闭,以缩短类加载时间。

验证的目的

保证二进制字节流中的信息符合虚拟机规范,并没有安全问题。

验证的必要性

虽然Java语言是一门安全的语言,它能确保程序猿无法访问数组边界以外的内存、避免让一个对象转换成任意类型、避免跳转到不存在的代码行.也就是说,Java语言的安全性是通过编译器来保证的.但是我们知道,编译器和虚拟机是两个独立的东西,虚拟机只认二进制字节流,它不会管所获得的二进制字节流是哪来的,当然,如果是编译器给它的,那么就相对安全,但如果是从其它途径获得的,那么无法确保该二进制字节流是安全的。

通过上文可知,虚拟机规范中没有限制二进制字节流的来源,在字节码层面上,上述Java代码无法做到的都是可以实现的,至少语义上是可以表达出来的,为了防止字节流中有安全问题,需要验证!

验证的过程

在这里插入图片描述

  • 文件格式验证
    验证字节流是否符合Class文件格式的规范,并且能被当前的虚拟机处理.
    本验证阶段是基于二进制字节流进行的,只有通过本阶段验证,才被允许存到方法区
    后面的三个验证阶段都是基于方法区的存储结构进行,不会再直接操作字节流。
    印证【加载和验证】是交叉进行的:
    • 1.加载开始前,二进制字节流还没进方法区,而加载完成后,二进制字节流已经存入方法区
    • 2.而在文件格式验证前,二进制字节流尚未进入方法区,文件格式验证通过之后才进入方法区
    • 也就是说,加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特 定数据结构存储至方法区中,继而开始下阶段的验证和创建Class对象等操作
  • 元数据验证
    对字节码描述信息进行语义分析,确保符合Java语法规范.
  • 字节码验证
    本阶段是验证过程的最复杂的一个阶段。
    本阶段对方法体进行语义分析,保证方法在运行时不会出现危害虚拟机的事件。
    字节码验证将对类的方法进行校验分析,保证被校验的方法在运行时不会做出危害虚拟机的事,一个类方法体的字节码没有通过字节码验证,那一定有问题,但若一个方法通过了验证,也不能说明它一定安全。
  • 符号引用验证
    发生在JVM将符号引用转化为直接引用的时候,这个转化动作发生在解析阶段,对类自身以外的信息进行匹配校验,确保解析能正常执行。
准备

仅仅为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即零值,这里不包含用final修饰的static,因为final在编译的时候就会分配了(编译器的优化),同时这里也不会为实例变量分配初始化。类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
准备阶段主要完成两件事情:

  • 为已在方法区中的类的静态成员变量分配内存
  • 为静态成员变量设置初始值,初始值为0、false、null等
解析

解析是虚拟机将常量池的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池中的CONSTANT_Class_info 、 CONSTANT_Fieldref_info 、 CONSTANT_Methodref_info 、CONSTANT_InterfaceMethodref_info四种常量类型。

  • 1:类或接口的解析:
    判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。
  • 2:字段解析:
    对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束(优先从接口来,然后是继承的父类.理论上是按照上述顺序进行搜索解析,但在实际应用中,虚拟机的编译器实现可能要比上述规范要求的更严格一些。如果有一个同名字段同时出现在该类的接口和父类中,或同时在自己或父类的接口中出现,编译器可能会拒绝编译).
  • 3:类方法解析:
    对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。
  • 4:接口方法解析:
    与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值