深入理解JVM( 一 )--类加载过程详解

巧了我就是萌 提交于 2020-02-06 06:20:21

深入理解JVM( 一 )–类加载过程详解

如果向学习和理解JVM的话,《深入理解JVM虚拟机》这本圣经必不可少的,我也是你们其中一份子,这本书看了一遍后有很多不懂,沮丧。。。也不敢装懂,不过该懂得还是懂一些,因此打算写几篇JVM得博客,巩固知识,也希望大伙能提点我的错误和不足

《深入理解JVM虚拟机》的分享链接:链接:https://pan.baidu.com/s/1fnTMWfC7Zi3HVJ5C78O80Q
提取码:hhmo

一、Java文件从编码完成到最终执行过程

  • 编译:Java文件通过javac命令编译生成字节码,也就是.class文件
  • 运行:.class文件交给JVM虚拟机进行类加载,到最后执行过程

二、类加载过程详解

1、类加载的过程

  • 过程: JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。

  • 注意点:JVM不是一开始把所有的类加载进内存,而是需要的时候有且仅有一次加载

2、类加载五个阶段:

类加载的顺序除了解析之外是固定的( 可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定。例如多态 ),但是不是连续( 因为这些阶段通常都是互相交叉的混合进行,通常会在一个阶段执行的过程中调用或者激活另一个阶段 )

2.1 加载:

  • 加载:将class文件通过类加载器存到内存中
    • 过程:
      1. 通过一个类的全限定名来获取定义此类的二进制字节流。
      2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
      3. Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
    • 注意点:类的全限定名获取的来源没有确定,可以很多途径《深入理解Java虚拟机》书中有提,不会坑你们,嘻嘻
    • .class文件来源:
      1. 本地磁盘
      2. 网络下载
      3. 下载的war,jar包
      4. 从专门的数据库中读取. class文件(少见)
      5. 将java源文件动态编译成 class文件1) 典型的就是动态代理,通过运行期生成 class文件 2) jsp转换成的servlet,而serlvet是一个java文件,会被编译成 class文件
    • 类加载器:( 这个在第二篇就会详细讲解 )
      • 启动类加载器:负责加载JRE的核心类库,如jre目标下的rt.jar,charsets.jar等
      • 扩展类加载器:负责加载JRE扩展目录ext中JAR类包
      • 系统类加载器:负责加载ClassPath路径下的类包
      • 用户自定义加载器:负责加载用户自定义路径下的类包

2.2 链接:包含验证、准备、解析三个过程

2.21 验证
  • 验证: 为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

  • 四种检验:

    1. 文件格式的验证
      • 常量中是否有不被支持的常量?
      • 指向常量的索引值是否指向不存在或不符合类型的常量等。
      • 目的:保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个Java类型信息的要求。
    2. 元数据的验证
      • 该类是否继承了被final修饰的类?
      • 类中的字段,方法是否与父类冲突?是否出现了不合理的重载等?
      • 目的:类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。
    3. 字节码的验证
      • 方法体的类型转换是否有效?例如:子类对象可以赋值于父类数据类型,反之发报错。
      • 任意时刻操作数栈的指令类型与指令代码序列是否配合工作。例如:操作数栈放置int类型,使用确实long类型载入本地变量表。
      • 目的:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。是对类的方法体的校验
    4. 符号引用的验证
      • 校验符号引用中通过全限定名是否能够找到对应的类?
      • 校验符号引用中的访问性(private,public等)是否可被当前类访问?
      • 目的:确保解析动作能正常执行
2.22 准备
  • 准备:为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。

  • 注意点:

    1. 类变量(被 static修饰的变量),而不包括实例变量,实例变量将在对象实例化时随着对象一起分配在Java堆中。
    2. 这里所说的初始值“通常情况”下是数据类型的零值,引用类型是null。特殊情况:被final修饰的变量,会直接赋值给定值
  • 例子:

    public static int value = 123   // 准备阶段是:value=0,而不是123
    public static final int value = 123   // 准备阶段是:value=123
    
2.23 解析
  • 解析:虚拟机将常量池内的符号引用替换为直接引用的过程

  • 符号引用(Symlxiuc References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量, 只要使用时能无歧义地定位到目标即可。符号引用是与虚拟机实现的内存布局无关

  • 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关

2.3 初始化

  • 初始化:执行构造器<clinit>方法的过程

  • 与准备阶段的差别:

    • 准备阶段,变量已经赋过一次系统要求的初始值( 默认值 )
    • 初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其他资源
  • <clinit>方法描述:是由编译器自动收集类中的所有类变量的赋值动作静态语句块( static块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的

  • 初始化分为主动引用被动引用

    • 主动引用:主动触发类的初始化
      • 对于初始化阶段虚拟机规范是严格规定了如下几种情况,如果类未初始化会对类进行初始化。
      1. 创建类的实例
      2. 访问类的静态变量( 不被final修饰的变量 )
      3. 访问类的静态方法
      4. 反射
      5. 当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
      6. 虚拟机启动时,定义了**main()**方法的那个类先初始化
    • 被动引用:不触发类的初始化
      • 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化
      • 通过数组定义来引用类,不会触发类的初始化
      • 访问类的常量,不会初始化类( 注意跟主动引用2情况区分 )
  • 测试用例( 要用System.out.println("***********主动引用X*********************");分隔每个用例,要一个一个测试,不要全部,不然会造成误解。因为类有且仅有一次初始化,全部运行,运行结果又造成误解 )

    package cn.deschen.jvm.gc;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MainClass {
    
        static {
            System.out.println("MainClass init 1");
        }
    
        public MainClass() {
            System.out.println("MainClass init 2");
        }
    
        public static void main(String[] args) throws ClassNotFoundException {
    //        System.out.println("***********主动引用7*********************");   //主动引用7,直接运行,就可以了,结果如下图,其他大家可以自己测试
            
    //        System.out.println("***********主动引用1*********************");
    //        new SuperClass();
    //        System.out.println("***********主动引用2*********************");
    //        System.out.println(SuperClass.value);
    //        System.out.println("***********主动引用3*********************");
    //        SuperClass.method();
    //        System.out.println("***********主动引用4*********************");
    //        Class.forName("cn.deschen.jvm.gc.ExtendClass");
    //        System.out.println("***********主动引用5*********************");
    //        System.out.println(ExtendClass.value);
    
    
    //        System.out.println("***********被动引用1*********************");
    //        System.out.println(SuperClass.value);// 被动引用1
    //        System.out.println("***********被动引用2*********************");
    //        SuperClass[] superClasses = new SuperClass[10];// 被动引用2
    //        System.out.println("***********被动引用3*********************");
    //        System.out.println(SuperClass.VALUE);// 被动引用3
    
        }
    
    }
    
    class SuperClass {
        static {
            System.out.println("SuperClass init 1");
        }
    
        public static int value = 123;
    
        public final static int VALUE = 456;
    
        public SuperClass(){
            System.out.println("SuperClass init 2");
        }
    
        public static void method() {
            System.out.println("ExtendClass static Method");
        }
    }
    
    class ExtendClass extends SuperClass {
        static {
            System.out.println("ExtendClass init 1");
        }
    
        public static int value = 123;
    
        public ExtendClass(){
            System.out.println("ExtendClass init 2");
        }
    
    }
    
  • 测试结果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qmlTk2Zj-1580884042274)(C:\Users\20423\AppData\Roaming\Typora\typora-user-images\1580884002599.png)]

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!