美文网首页
2020-09-28---第七章--虚拟机类加载机制

2020-09-28---第七章--虚拟机类加载机制

作者: 李霖神谷 | 来源:发表于2020-10-10 09:22 被阅读0次

    1.类在虚拟机运行的生命周期:
    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中验证、准备和解析三个部分统称为连接(Linking)。

    2.初始化:
    1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
    2)使用java.lang.reflect包的方法对类进行发射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
    3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
    4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

    对于这四种会触发类进行初始化的场景,虚拟机规范中使用了一个很强烈的限定语:“有且只有”,这四种场景中的行为称为对一个类进行主动引用。除此之外所有引用类的方式,都不会触发初始化,称为被动引用。下面举三个例子来说明被动引用,分别见代码清单7-1、代码清单7-2和代码清单7-3。
    7-1:

    package com.shuai.Util;
    
    class  father{
    static {
        System.out.println("father");
    }
     public  static  String name="shuai";
    
    }
    class  children extends  father{
     static {
         System.out.println("children");
     }
    
    
    
    }
    
    
    public class test3 {
     public static void main(String[] args) {
         System.out.println(children.name);
     }
    
    }
    
    

    上面运行的结果是father shuai不会运行children。当通过子类调用父类的静态代码时不会初始化本身的静态代码块。

    7-2:

    public class test3 {
        public static void main(String[] args) {
            children[] c=new children[10];
        }
    }
    

    使用数组的时候不会初始化。

    7-3:

    class  father{
       static {
           System.out.println("father");
       }
        public  static  final  String HELLOWORD="hello world";
    
    }
    
    
    public class test3 {
        public static void main(String[] args) {
            System.out.println(father.HELLOWORD);
        }
    
    }
    

    上述代码运行之后,也没有输出“father”,这是因为虽然在Java源码中引用了father类中的常量HELLOWORLD,但是在编译阶段将此常量的值“hello world”存储到了thest类的常量池中,对常量father.HELLOWORLD的引用实际都被转化为test类对自身常量池的引用了。也就是说实际上father的Class文件之中并没有father类的符号引用入口,这两个类在编译成Class之后就不存在任何联系了。

    3.类的加载过程:
    包括加载、验证、准备、解析、初始化。
    3.1加载:
    在加载阶段,虚拟机需要完成以下三件事情:
    1)通过一个类的全限定名来获取定义此类的二进制字节流。
    2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

    获取字节流的方式:
    □ 从ZIP包中读取,这很常见,最终成为日后JAR、EAR、WAR格式的基础。
    □ 从网络中获取,这种场景最典型的应用就是Applet。
    □ 运行时计算生成,这种场景使用得最多的就是动态代理技术,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass来为特定接口生成*$Proxy的代理类的二进制字节流。
    □ 由其他文件生成,典型场景:JSP应用。□ 从数据库中读取,这种场景相对少见些,有些中间件服务器(如SAPNetweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
    □ ……

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

    为什么需要验证:
    Class文件并不一定要求用Java源码编译而来,可以使用任何途径,包括用十六进制编辑器直接编写来产生Class文件。在字节码的语言层面上,上述Java代码无法做到的事情都是可以实现的,至少语义上是可以表达出来的。虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作。

    过程:
    大致上都会完成下面四个阶段的检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。

    3.3 准备:
    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段
    3.4 解析:
    解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

    □ 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
    □ 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

    4.双亲委派:

    站在Java虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现[插图],是虚拟机自身的一部分;另外一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
    □ 启动类加载器(Bootstrap ClassLoader):前面已经介绍过,这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。
    □ 扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.LauncherExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。 □ 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc. LauncherAppClassLoader来实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    过程:
    双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

    相关文章

      网友评论

          本文标题:2020-09-28---第七章--虚拟机类加载机制

          本文链接:https://www.haomeiwen.com/subject/ertduktx.html