• Java虚拟机--类加载机制


    1.什么是类加载机制

      虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

    2.类的加载时机

                

      图中的七个阶段代表这类的生命周期,其中加载验证准备初始化卸载的顺序是确定的,按这种顺序按部就班的“开始”,而解析则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定。另外要注意:刚才的5个阶段通常都是相互交叉地混合式进行,通常会在一个阶段执行的过程中调用、激活另外一个阶段。

      加载阶段何时开始没有做强制约束,但对于初始化阶段,虚拟机规范则规定了有且只有5种情况必须对类进行“初始化”:  

      ■ 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时。触发场景:new关键字实例化对象、读取或设置一个类静态字段(被final修饰、已在编译器放入常量池的除外)、调用另一个类的静态方法。

      ■ 使用java.lang.reflect包的方法对类进行反射调用时。

      ■ 初始化一个类时,如果发现其父类还没初始化,就要先初始化父类。

      ■ JVM启动时,用户指定一个可执行的主类,先初始化这个主类。

      ■ 当使用JDK 1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,切方法句柄所对应的类没有初始化时。

    3.类的加载过程

      加载

      加载阶段JVM完成件事:

      ■ 通过类的全限定名来获取定义此类的二进制数据流。(如果没有找到对应类文件,只有在类实际使用时才抛出错误)  

      ■ 将数据流分析并转化为方法区(JVM对运行数据区的划分结构,包括堆、栈、方法区等)的运行时数据结构。另外,这里处理了部分检验,如对类文件的魔数的验证、检查文件是否过长或过短、确定是否有父类。

      ■ 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。

      连接

      连接指的是将Java类的二进制代码合并到JVM的运行状态之中的过程,连接之前必须被成功加载。连接分三个步骤:

      ■ 验证:用来确保Java类的二进制表示在结构是是完全正确的、符合虚拟机规范的。如文件格式验证(是否以魔数0xCAFEBABE开头、主次版本号是都被虚拟机支持、常量类型是否都被支持)、元数据验证(就是语义分析,如是否有父类、这个类的父类是否继承了不被允许的类等)、字节码验证(通过数据流和控制流分析,确定程序语义是合法的)、符号引用验证(将符号引用转化为直接引用,发生在解析阶段),如果验证出现错误的话,会抛出java.lang.verifyError错误异常。

      ■ 准备:正式为类变量(被static修饰)分配内存并设置类变量初始值(数据类型的零值)的阶段,这些变量所使用的内存都将在方法区(持久代)中进行分配。

      ■ 解析:将常量池内的符号引用替换为直接引用的过程,目的是确保这些被引用的类能被正确的找到。符号引用:以一组符号来描述引用的目标,符号可以是任意无歧义的字面量。直接引用:可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

      初始化

      类的初始化是延迟的,直到第一次被主动使用(active use),JVM才会初始化类。初始化过程的主要操作是执行静态代码块和初始化静态域,在一个类被初始化之前,它的直接父类也要被初始化,但是一个接口的初始化不会引起其父接口的初始化。

    4.类加载的推动者--类加载器

      Java中的所有类,必须被加载到JVM中才能运行,这个加载的工作是由JVM中的类加载器完成,类加载器的实质是把类文件从硬盘读取到内存中,JVM加载类时都是通过ClassLoaderloadClass()方法,此方法使用了双亲委派模式

      接下来我们看loadClass方法中双亲委派的实现:

        

      图中有一个同步代码块synchronized (getClassLoadingLock(name)),我们来看getClassLoadingLock(name)的作用是什么:

        

      我们这里看到parallelLockMap变量,根据这个变量进行不同的操作,如果变量是null,直接返回this;如果不为null,就新建一个对象,调用putIfAbsent方法给刚建好的对象赋值,那这个变量的来源是哪?

        

      我们发现这个变量是ClassLoader类的成员变量,这个变量的初始化工作在ClassLoader的构造函数中:

        

      这里我们可以看到构造函数根据一个属性ParallelLoadersRegistered状态的不同来给parallelLockMap 赋值。 那ParallelLoaders又来自何方呢?

        

      我们发现,在ClassLoader类中包含一个静态内部类private static class ParallelLoaders,在ClassLoader被加载的时候这个静态内部类就被初始化。这个类的意思就是:封装了并行的可装载的类型的集合

      综上源码,getClassLoadingLock(String className)的作用:为类的加载操作返回一个锁对象。为了向后兼容,如果当前的classloader对象注册了并行能力,方法返回一个与指定的名字className相关联的特定对象,否则,直接返回当前的classloader对象。我们看到,classloader源码中还有一个resolveClass方法,它的作用是:链接指定的类,这个方法给classloader用来链接一个类,如果这个类已经被链接过了,那么这个方法只做一个简单的返回;否则,这个类将被按照Java™规范中的Execution描述进行链接。

    5.类加载器总结

      java中的类大致分三种:系统类、扩展类、程序员自定义的类。

      类加载方式有两种:隐式加载,程序在运行过程中碰到new关键字等方式生成对象时,隐式调用类加载器;显式加载,通过Class.forName()等方法,显式加载类。

      类加载的动态性体现:当运行一个由n多类组成的应用程序时,它会先把保证程序运行的基础类一次性加载入JVM中,其他类当用到时再进行加载,这样节省了内存开销。

      Java类加载器:加载器的实质也是类,功能是把类加载入JVM,但JVM的加载器有三个,一方面是是各自负责各自的区块,另一方面实现委托模型。其层次结构:

            

      类加载器之间协调工作:那当我们需要加载一个类时,是由哪一个加载器来加载的呢?在这里Java采用委托模型机制,这个机制就是“类加载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent找不到,再依照自己的搜索路径搜索类”。比如我们本地有个Test类,调用Test.class.getClassLoader()时获得的加载器为AppClassLoaderAppClassLoader的父加载器为ExtClassLoaderExtClassLoader的父加载器为根加载器,但在IDE控制台打印时为null,是因为它由C++编写,在Java中看不到它。默认情况下,采用AppClassLoader加载程序类

      采用“委托机制”是从安全考虑的。试想,如果自定义了一个"java.lang.string"的恶意类,且其被加载入JVM中,会引起严重的后果。而采用“委托机制”,这个类永远只会由跟加载器加载。

        

  • 相关阅读:
    win10下查看进程,杀死进程
    Pycharm,debug调试时怎样带参数
    struts2,登录功能模块实现
    struts2处理.do后缀的请求
    struts2 修改action的后缀
    j2ee中如何拦截jsp页面?
    4个好用的JS联动选择插件
    css position:absolute 如何居中对齐
    使用jquery插件报错:TypeError:$.browser is undefined的解决方法
    phpcms v9后台多表查询分页代码
  • 原文地址:https://www.cnblogs.com/lemon-pomelo/p/9269969.html
Copyright © 2020-2023  润新知