虚拟机类加载机制

2018-09-22  本文已影响0人  啦啦哇哈哈

类文件的基础知识

这一块知识之前,是要了解一些关于类文件的基础知识。

虚拟机实现了平台无关性,这已经很熟悉了,而实现无关性的基石就是不同平台虚拟机都是用统一的程序存储格式——字节码(ByteCode),而无关性还有一层概念是——语言无关性,因为虚拟机只认字节码,所以有可能通过编译器将源程序编译成字节码的语言,都有在JVM上运行的可能性。字节码文件也就是我们常说的Class文件,.java文件通过javac编译成.class文件,然后JVM执行它。而书上介绍的还有其他各种语言都有相应的编译器可以把它们编译成.class文件,都可以在JVM上面运行,虚拟机只关心存储字节码的class文件,而不关心它是从哪里来的。

class文件是一组以8位字节为基础单位的二进制流,它之中包含了Java虚拟机指令集和符号表以及若干其它辅助性喜,有相关的强制性语法和结构化约束,语言的各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成的。

类加载

类加载是指虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型的过程。
Java语言中,类型的加载、链接和初始化过程都是在程序运行期间完成的。

类从被加载到虚拟机内存中开始、到卸载出内存为止,整个生命周期包括7阶段:

加载是类加载过程的第一个阶段.

对Java语言来说就是将class文件字节码内容加载到内存中,并将这些静态数据(这个类的静态变量、静态方法、常量池以及类的代码等等)转换成方法区中的运行时数据结构,接着同时生成一个代表这个类的java.lang.Class对象(就是反射的那个对象,我们的反射技术就是利用这个类才进入方法区的,这个对象也没有指定是在Java堆中,例如HotSpot虚拟机的Class对象是在方法区里面)作为方法区数据的访问入口。这个过程需要类加载器参与。
类加载器的内容后面会介绍。
加载阶段可能尚未完成,连接阶段就开始了!

验证阶段为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

那么,什么情况会危害JVM的安全呢?Java语言比较安全,没有c++里面那些指针什么的,而且如果访问数组边界以外的数据、非法转型等等操作,编译器都会拒绝编译。但是我们一开始就说了Class文件来源是任何途径的,甚至可能直接编写。。那上面这些Java语言做不到的事情都是有可能在语义上表达出来,让它发生的。JVM如果不检查输入的字节流,那就有可能导致系统崩溃。
所以验证阶段十分重要!验证阶段包含四个阶段:

准备阶段正式为类变量分配内存并设置类变量初始值。这些变量所使用的内存都将在方法区进行分配。

注意!是为类变量分配内存和为类变量设置初始值,即static变量!不包括实例变量!实例变量不在方法区里,在Java堆里面。
例如:

public static int value = 123;
public int book;

在准备阶段过后value初始值是0而不是123,赋值123的动作在初始化阶段才完成!而这个book甚至不会被分配内存,因为准备阶段无关实例变量!book是一个实例变量。

不同类型的变量有不同的零值。除了常规意义上的表数字的零值都是“0”以外,boolean是false,reference是null,char是‘\u0000’。

解析阶段是JVM将常量池内的符号引用替换为直接引用的过程。

符号引用和直接引用都是类文件中的内容,都是个啥。。初学挺难解释的,这里有一个很强的回答可以在姿势水平够之后进行参考

JVM里的符号引用如何存储? - RednaxelaFX的回答 - 知乎
https://www.zhihu.com/question/30300585/answer/51335493

下面引用这个问题的回答下面一个用比较通俗解释符号引用和直接引用的答案。

2.字段解析
对字段进行解析时,会先在去解析字段所属的类或接口,解析成功后,在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束还没有查找到,就抛出异常。
如果成功返回引用,将会对字段进行权限验证,如果发现不具备对该字段的访问权限,就会抛出异常。

3.类方法解析
对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口(因此在父类和接口方法名冲突时候,父类方法优先级高!)。 最后同样的验证权限。

4.接口方法解析
与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。最后依然要验证访问权限。

是类加载过程的最后一步,会开始真正执行类中定义的Java字节码。而之前的类加载过程中,除了在『加载』阶段用户应用程序可通过自定义类加载器参与之外,其余阶段均由虚拟机主导和控制。
我们在准备阶段就提到了初始化,准备阶段是给类变量分配空间赋初始零值。而初始化阶段则会根据Java程序的设定去初始化类变量和其他资源。
它是执行类构造器<clinit>()方法的过程。

<clinit>():由编译器自动收集类中的所有类变量的赋值动作静态语句块static{}中的语句合并产生。编译器收集顺序是根据语句在源文件中的出现顺序来决定的。静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不能访问。

public class Test{
  static{
    i=0;                              //这一句赋值可以正常编译通过
    System.out.println(i);   //这一句访问会提示“非法向前引用”
  }
  static int i = 1;

注意以下几点:

最后检验一下学会了没。。看这个分析!
一个有趣的题目

上一篇 下一篇

猜你喜欢

热点阅读