我在跟着[张龙老师]学习JVM 之 类加载器深入解析与阶段分析

类加载(classloading)

  • 在java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的。
      • 类型
        • 是指我们定义的类(class)、接口(interface),枚举(enum)等,这里并不涉及到对象的概念。
        • 在类加载过程当中我们绝大多数场景提到的都是所谓的类或类型(他表示为object本身,而不是他的对象)。
        • 在实际开发中,我们绝大多数操控的是类的实例,而不是类,但是创建类的实例需要类的存在,才能完成构造类的实例。
        • 类型是在运行期间完成的,在编译期间不存在的(编译只是将我们的程序(.java 文件)编译成字节码文件(.class文件),这个需要搞明白),换句话说他是 runtime 概念。
        • java开发人员这么做的原因是让开发人员在开发期间遇到很多很多可能性(多态)。
        • java 本身是一门静态语言,但是很多特性又具有动态语言的特性。
      • 加载
        • 最常见(不是唯一)的场景就是将编译好的class文件,从磁盘加载到内存中。
      • 连接
        • 连接需要完成很多处理过程,用比较简洁的语言表述,就是将类与类的关系确定好,对于一些字节码相关处理(如:验证、准备、解析 )都是在类的连接中完成的
        • java 最开始加载的肯定是字节码,只要字节码没有问题,才能真正被虚拟机所执行。
        • 虽然字节码是编译之后生成出来的,是由编译器帮我们的进行编译的,所以通常不会出错(这是一个理想状态)。但是字节码也是可以被更改的(凡是能被人操控,那么不可控的因素就有了)。我在实际开发中就曾经专门对一个字节码文件进行修改。所以在类的连接中就会去校验字节码。
        • 最典型的就是将类与类之间的符号引用转换成直接引用,也是在这个阶段完成的。
    初始化
      • 典型就是对类的静态变量赋值,就是在初始化完成的。

    

在类的加载、连接、初始,会按照这个顺序,但是并不是严格按照这个顺序,在有些情况也不会严格按照这个顺序,这个是和虚拟机有关系的。
整个过程,JVM 都是都严格的规定的,但是在某些地方只是做了简单的描述。

 

 

 类加载器深入解析 

  • 类加载器作用  
    java中的每一个类型(如:java.lang.String、java.util.Date),最终他们的数据结构信息会被纳入到 JVM 管辖范围内,也就进入内存当中。
    但他们又是怎么进入的到内存中的呢?这是由类加载器完成的。
    换句话说,什么叫类加载器(ClassLoader),就是用来加载类的一个工具。
  •  类加载器组成模型
类加载器用来把类加载到Java虚拟机中,从JDK1.2版本开始,类的加载过程采用父亲(双亲)委托机制,这种机制能更好的保证java 平台的安全。
在此委托机制中,除了java 虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当java 程序请求加载器loader加在Sample类外,loader1首次委托自己的父类加载器去加载Sample类,若父类加载器能加载,则由父加载器完成加载任务,
否则才由加载器loader1本身加载Sample类。若都不能加载,则报错

 

  • java 虚拟机与程序的生命周期
  • 在如下几种情况下,java 虚拟机将结束生命周期
    • 显示执行了System.exit()方法
      • 在调用 System.exit() 会返回一个code值,作为一个结束标识。
    • 程序正常执行结束(实际情况)
    • 程序在执行过程中遇到了异常或错误而异常终止(实际情况)
    • 由于操作系统出现错误而导致Java虚拟机进程终止(心塞情况)

 类的加载、连接与初始化

  • 加载
    • 查找并加载类的二进制数据并保存java虚拟机中。
    • 类的加载的最终产品是位于内存中class对象。
      Class 对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。(class对象是整个java反射的入口)
    • 有两种类型的类加载器
    • 类加载器并不需要等到某个类被“首次主动使用”时才去加载它。 
      1:JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)
      2:如果这个类一直没有被程序主动使用,那么 类加载器就不会报告错误。

       

  • 连接
    类被加载后,就进入链接阶段。链接就是将已经读入到内存中的类的二进制数据合并到虚拟机的运行时环境中去。
    • -验证:
      • 确保被加载的类的真确性(确保字节码未被恶意篡改,并且是符合JVM规范的)
      • 类文件的结构检查
      • 语义检查
      • 字节码验证
      • 二进制兼容性的验证
    • -准备:为类的静态变量分配内存 (整个时候类加载还没完成,所以不存在类的实例,还没有类的实例变量),并将其初始化为默认值(Java中每个类型都有默认值,如:int为0,double,float 默认为0.0,String默认为 null等等)。
      在准备阶段,java 虚拟机为类的静态变量分配内存,并设置默认的初始值。
      在准备阶段,将为int类型的静态变量 分配4个字节的内存空间,并赋予默认值0,
      为long类型的静态变量分配八个字节的内存空间,并赋予默认值0
      不得不强调的是,我们创建一个类,如下,给name给个默认值 “张三”。
      
      public class Student{
        private static String name='张三'  
         
      }
      
      在准备阶段这个过程当中,虚拟机并不会讲 “张三” 赋值给 name 这个属性,而是给 name属性一个默认值 (null)。

      对于中间过程是这样的

      将 Student 这个类 加载到内存当中,进入 连接的准备阶段 的时候,他会为name 分配一个内存,并将name赋值为默认值(null)。
      而不是“张三”,“张三”这个值是在最后某个阶段赋值上去的。
    • -解析:把类中的符号引用转换为直接引用
      符号引用:一种间接的引用方式,比如一个类里方法引入了另一个类。
      
      直接引用:就是直接将这个类通过指针的方式指向了目标对象的内存地址。

      解析过程就是将类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用的过程。
  • 初始化
    • 为类的静态变量赋予正确的初始值 
      再来看这个例子:
      
      public class Student{
        private String name='张三'  
         
      }
      
      当类完成解析阶段,进入初始化阶段,在这阶段虚拟机会为类的静态变量赋予正确的初始值(也就是“张三”)。
    • 类的初始化步骤:
      • 若这个类还没有被加载和链接,那就先进行加载和链接
      • 若该类存在父类,并且这个父类还没有被初始化,那就先初始化直接父类。
      • 若该类存在初始化语句,那就依次执行这些初始化语句。
    • 类的初始时机(参考类的七种主动使用)
  • 使用
    • 顾名思义就是指正常调用该类(如:创建对象、调用方法等)
  • 卸载
    • 字节码文件加载到内存中形成了一种数据结构,驻留在内存中。当类一旦被卸载后,就不能再使用该类(ClassNotFoundException),当然可以重新加载到内存里面。

 总结:

   一个类(类型)的生命周期,大致就可以分为这五个阶段 加载、连接(验证、准备、解析)、初始化、使用、卸载。

 

posted @ 2019-11-13 20:59  不朽_张  阅读(315)  评论(0)    收藏  举报