zoukankan      html  css  js  c++  java
  • Java虚拟机的类加载机制


    title: Java虚拟机的类加载机制
    date: 2018-10-18 16:25:15
    tags:
    categories:

    一、虚拟机的类加载机制

    我们先看看类加载机制的定义,再来说法这一个加载流程。《深入理解JVM虚拟机》第二版中是这么解释的:虚拟机吧描述类的数据从Class问价加载到内存并对数据进行校验/转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
    另外要注意的是,Java语言中,类型的加载/连接和初始化过程都是在程序运行期间完成的,这个相当于C++中的链接过程。

    二、类的生命周期

    然后每个Class文件都有可能待变Java语言中的一个类或者接口,我个人理解,Java中类和接口本质是一样的。另外Class文件在jvm中可以是各种形式的,只要是二进制流就可以,不一定需要是本地磁盘文件。
    类的生命周期
    上图中类加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,累的加载过程必须按照这种顺序按部就班的开始,而解析的过程则不一定。

    三、什么时候开始加载

    虚拟机规范中没有要求,所以不同的虚拟机可以不同的实现方式,但是初始化时严格规定必须对类立刻进行初始化

    1. 遇到new、getstatic、putstatic或invokestatic这四条字节指令码,如果类没有进行初始化,则需要先触发其初始化。
    2. 使用java.lang.reflect包的方法对类进行反射调用的时候,与上相似。
    3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
    4. 当虚拟机启动时,用户需要制定一个要执行的主类,虚拟机会先初始化(包含main函数)这个主类。
    5. JDK1.7中,如果一个java.lang.invoke.MetodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄所对应的类没有过进行过初始化,则需要初始化。

    这里要注意的是,虽然没有说什么时候要加载,但当需要初始化的时候,必须要按上图的顺序,逐步进行。但是进行不一定是同步的,可能是交叉的,例如解析可能是可以在初始化之后的,就是说我开始了解析之后,可能不会完成,而是先去初始化,再进行解析。

    除此之外,所有引用类的方式都不会触发其初始化,成为被动引用。

    四、类的加载过程

    加载

    加载类加载过程的第一个阶段(整一个从加载到初始化的这么一个过程称为类加载),这个阶段需要完成三件事。

    1. 通过一个类的全限定名来获取定义此类的二进制字节流。
    2. 将这个字节流所代表的的静态存储结构转化为方法去的运行时数据结构。
    3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
      另外,类加载时通过类加载器完成的,具体由什么加载器可以用开发人员控制,开发人员既可以使用系统的引导类加载器,也可以用户自定义的类加载器完成。(重写一个Classloader)
      对于数组而言,数组类并不是通过类加载来创建的,但是数组元素是通过类加载器创建的。具体规则请查阅《深入理解jvm虚拟机第二版》P215,里面详细介绍了关于数组类的加载机制。

    验证

    验证是图1中,连接阶段的第一个步骤,目的是保证Class文件的字节流中包含的信息符合虚拟机的规范,如果不符合规范,并且保证虚拟机的安全就会报错。验证阶段大致上是分成四个阶段:

    1. 文件格式验证

    第一阶段要验证字节流是否符合Class文件格式的而规范,并且被当前版本的虚拟机处理。

    2. 元数据验证

    第二阶段是对字节码描述的信息进行语义分析,保证其描述的信息符合Java语言的规范要求。

    3. 字节码验证

    第三阶段是通过数据流和控制语句分析,是否存在逻辑错误,语义是否合法。

    4. 符号引用验证

    最后一个阶段是发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外的信息进行匹配性校验,目的是为了确保解析动作能正常执行。(是否可以正确找到引用的类)

    准备

    准备阶段是正式为类变量分配内存病设置类变量初始值的阶段(static变量)这些变量所使用的内存都讲在方法去中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下。

    • 首先,这时候进行内存分配的仅包括类变量,不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
    • 其次,这里所说的初始值通常情况下是数据类型的零值。如下
      public static int value = 123;
      这个阶段的赋值实际上是初始值0,而不是123,123是在初始化阶段才赋值的,也就是clint(并非new初始化,而是类初始化clint()方法)。

    解析

    在JVM中类加载过程中,在解析阶段,Java虚拟机会把类的二级制数据中的符号引用替换为直接引用。

    符号引用(Symbolic References):

      符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

    直接引用:

    直接引用可以是
    (1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
    (2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
    (3)一个能间接定位到目标的句柄
    直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。

    初始化

    类初始化阶段是类加载过程的最后一步,实际上就是执行clinit()方法的过程。和init方法不同,clinit是对类进行初始化,而java类文件中,init()方法是在构造对象的时候,对对象进行初始化,这点一定要分清楚。

    类加载器

    类加载就是通过一个类的全限定名来获取描述此类的二进制字节流,就是我们之前提到的.class文件中的内容。这个动作是在Java虚拟机的外部实现的,所以可以有自定义类加载器。实现这个加载的动作代码模块,就叫做类加载器

    类与类加载器

    1. 类加载器都有一个独立的类名称空间。这意味着同一个类加载器加载的类才可以进行比较相等,否则,即便是同一个类,由不同的类加载器加载,也是不同的。

    2. 上述所说的比较相等,包括类的Class对象的equals()方法,isAssignavleFrom()方法,isInstance()方法的返回结果,也包括instanceof关键字做对象所属关系判定等情况。

       public class ClassLoaderTest {
       public static void main(String[] args) throws Exception{
       ClassLoader myLoader = new ClassLoader() {
           @Override
           public Class<?> loadClass(String name) throws ClassNotFoundException {
               try {
                   String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                   InputStream inputStream =getClass().getResourceAsStream(fileName);
                   if (inputStream==null){
      
                       return super.loadClass(name);
                   }
                   byte[] b = new byte[inputStream.available()];
                   inputStream.read(b);
                   return defineClass(name, b, 0, b.length);
               } catch (IOException e) {
                   e.printStackTrace();
                   throw new ClassNotFoundException(name);
               }
           }
       };
       Object obj = myLoader.loadClass("Solution");
       System.out.println(obj.getClass());
       System.out.println(obj instanceof Solution);
       }
       }
      

    运行结果:

    class java.lang.Class   
    false
  • 相关阅读:
    Proj THUDBFuzz Paper Reading: PMFuzz: Test Case Generation for Persistent Memory Programs
    入围 WF 后训练记
    算法竞赛历程
    2021 多校 杭电 第十场
    2021 多校 杭电 第九场
    2021 多校 牛客 第十场
    2021 多校 牛客 第九场
    2021 多校 杭电 第八场
    2021 多校 杭电 第六场
    2021 多校 杭电 第七场
  • 原文地址:https://www.cnblogs.com/didadida/p/10144212.html
Copyright © 2011-2022 走看看