您当前的位置: 首页 > 
  • 1浏览

    0关注

    674博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

第七章--虚拟机类加载机制

沙漠一只雕得儿得儿 发布时间:2016-11-26 10:28:58 ,浏览量:1

一个java文件从被加载到被卸载这个生命过程,总共要经历5个阶段:
加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载
一.加载
加载阶段虚拟机 需要完成三件事情:
       1.通过一个类的全限定名来获取定义此类的二进制字节流。
       2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
       3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
解析:
关于1,很灵活,很多技术都是在这里切入,因为它并没有限定二进制流从哪里来:
从class文件来->一般的文件加载
从zip包中来->加载jar中的类
从网络中来->Applet
.........
相比与加载过程的其他几个阶段,加载阶段可控性最强,因为类的加载器可以用系统的,也可以用自己写的,程序猿可以用自己的方式写加载器来控制字节流的获取。
获取二进制流获取完成后会按照jvm所需的方式保存在方法区中,同时会在java堆中实例化一个java.lang.Class对象与堆中的数据关联起来。
二.验证
验证阶段需要验证的内容:
文件格式验证->元数据验证->字节码验证->符号引用验证
       1.文件格式验证:
       文件格式验证:验证点有:是否以魔数开头,验证字节流是否符合Class文件格式的规范并 验证其版本是否能被当前的jvm版本所处理。ok没问题后,字节流就可以进入内存的方法区进行保存了。后面的3个校验都是在方法区进行的,不会再直接操作字节流。
       2.元数据验证:
        对字节码描述的信息进行语义化分析,保证其描述的内容符合java语言的语法规范。验证点如下:
       (1)这个类是否有父类(除了java.lang.Object以外均有父类)
       (2)这个类的父类是否继承了不允许被继承的类(例如被final修饰的类)
       (3)如果这个类不是抽象类,是否实现了其父类或者接口之中要求实现的所有方法
       (4)类中的字段,方法是否与父类产生矛盾(例如覆盖了父类final修饰的字段,方法等)
   ................................
       3. 字节码检验: 最复杂,对方法体的内容进行检验,保证其在运行时不会作出什么出格的事来
       4.符号引用验证: 来验证一些引用的真实性与可行性
       (1)比如代码里面引了其他类,这里就要去检测一下那些来究竟是否存在;
       (2)代码中访问了其他类的一些属性,这里就对那些属性的可以访问性进行了检验。(这一步将为后面的解析工作打下基础)
三.准备
       这阶段会为类变量(指那些静态变量)分配内存并设置类比那辆初始值的阶段,这些内存在方法区中进行分配。这里要说明一下,这一步只会给那些静态变量设置一个初始的值,而那些实例变量是在实例化对象时进行分配的。
       这里的给类变量设初始值跟类变量的赋值有点不同,比如下面:  
       public static int value=123;  
       在这一阶段,value的值将会是0,而不是123,因为这个时候还没开始执行任何java代码,123还是不可见的,而我们所看到的把123赋值给value的putstatic指令是程序被编译后存在于(),所以,给value赋值为123是在初始化的时候才会执行的。  
       这里也有个例外:
        public staticfinal int value=123;  
       这里在准备阶段value的值就会初始化为123了。这个是说,在编译期,javac会为这个特殊的value生成一个ConstantValue属性,并在准备阶段jvm就会根据这个ConstantValue的值来为value赋值了。
四.解析
       将常量池中的符号引用替换为直接引用的过程。类似于地址重定位,将虚拟地址转化为实际的物理内存地址。
五.初始化
       在前面的类加载过程中,除了在加载阶段用户可以通过自定义类加载器参与之外,其他的动作完全有jvm主导,到了初始化这块,才开始真正执行java里面的代码。 这一步将会执行一些预操作,注意区分在准备阶段,已经为类变量执行过一次系统赋值了。 
       其中加载(除了自定义加载)+链接的过程是完全由jvm负责的,什么时候要对类进行初始化工作(加载+链接在此之前已经完成了),jvm有严格的规定(五种情况):
        1.遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,假如类还没进行初始化,则马上对其进行初始化工作。其实就是3种情况:用new实例化一个类时、读取或者设置类的静态字段时(不包括被final修饰的静态字段,因为他们已经被塞进常量池了)、以及执行静态方法的时候。 
       2.使用java.lang.reflect.*的方法对类进行反射调用的时候,如果类还没有进行过初始化,马上对其进行。 
       3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。
       4.当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),则jvm会先去初始化这个类。 
       5.用Class.forName(String className);来加载类的时候,也会执行初始化动作。注意:ClassLoader的loadClass(String className);方法只会加载并编译某类,并不会对其执行初始化。 以上5种预处理称为对一个类进行主动的引用,其余的其他情况,称为被动引用,都不会触发类的初始化。
       其实说白了,这一步就是执行程序的();方法的过程。下面我们来研究一下()方法: ()方法叫做类构造器方法,有编译器自动手机类中的所有类变量的赋值动作和静态语句块中的语句合并而成的,置于他们的顺序与在源文件中排列的一样。 ();方法与类构造方法不一样,他不需要显示得调用父类的();方法,虚拟机会保证子类的();方法在执行前父类的这个方法已经执行完毕了,也就是说,虚拟机中第一个被执行的();方法肯定是java.lang.Object类的。
关注
打赏
1657159701
查看更多评论
立即登录/注册

微信扫码登录

0.0362s