JVM加载类过程

1 类加载总过程

image-20210111225346396
image-20210111225412562
  • 类加载子系统负责从文件系统或者网络加载Class文件,Class文件在文件开头有特定的文件标识

    image-20210111225448746
    1. class file存在本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来,根据这个文件实例化n个一模一样的实例
    2. class file加载到JVM中,被称为DNA元数据模板,放在方法区
    3. 在.class文件-> JVM-> 最终成为元数据模板,此过程就要一个运输工具(Class Loader)
  • ClassLoader 只负责Class文件的加载,至于它是否能运行,则由Execution Engin 决定

  • 加载的类信息存放于一块成为方法区的内存空间。除了类的信息外(大的Class对象实例),方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字敞亮(这部分敞亮信息是Class文件中常量池部分的内存映射)

    1. 我们可以用javap -v ***.class来反编译字节码文件,然后查看里面的Class结构

      image-20210111225512451

      这里我们结构里面有一个常量池

1.1 ClassLoader加载class文件的过程

用图来演示就是这样

image-20210111225529466
  1. 我们需要运行HelloLoader的main方法,首先需要编译出来一个class文件
  2. class文件由ClassLoader加载到内存当中
  3. 判断是否已经装载过了,没装载的就进行装载,装载的就进行链接,初始化和调用main方法
  4. 如果装载有问题就会报错,比如我们常见的因为版本冲突的问题会报错找不到类

1.2 加载过程

  1. 通过一个类的全限定名获取定义此类的二进制字节流
    • 本地文件系统
    • 通过网络获取,web Applet
    • 从zip压缩包中读取,成为日后jar,war格式的基础
    • 运行时计算生成,使用最多的是:动态代理技术
    • 由其他文件生成,典型场景:JSP应用
    • 从专有数据库中提取.class文件,比较少见
    • 从加密文件中获取,典型的防Class文件被反编译的保护措施
  2. 将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

1.3 链接过程

image-20210111225544948

1.3.1 验证(vefiry)

  • 目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全

  • 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证

    比如说字节码文件要以CAFEBABE开头

    image-20210111225602521

1.3.2 准备(prepare)

  • 为类变量分配内存并且设置该类变量的默认初始值,即零值

  • 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化赋值

    如果被final修饰的话,就不是一个变量了,会变成一个常量,这时在编译的时候就会被分配了,只会在prepare的阶段显式初始化和赋值

  • 这里不会为实例变量分配初始化,类变量会分配在方法去,而实例变量实惠随着对象一起分配到java堆中

比如:

public class HelloApp {
    private static int a = 1;//prepare:a = 0 ---> initial : a = 1
    public static void main(String[] args) {
        System.out.println(a);
    }
}

a的值在prepare的时候会被初始化为0,而在initialization的时候才会被赋值为1

1.3.3 解析(resolve)

  • 将常量池内的符号引用转换为直接引用的过程

    image-20210111225614330

    比如说这些的标号就是一个符号引用

  • 事实上,解析操作旺旺会伴随着JVM在执行完初始化之后再执行

  • 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个简洁定位到目标的句柄。

  • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info,CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。

1.4 初始化

image-20210111225625923
  • 初始化阶段就是执行类构造器方法<clinit>()的过程

  • 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来

    image-20210111230645760

    比如该ClassinitTest的类,我们并没有声明一个clinit的方法,而是编译器自己根据类变量赋值和静态代码块来生成的。

    public class ClassInitTest {
       private static int num = 1;
    
       static{
           num = 2;
           number = 20;
           System.out.println(num);
           //System.out.println(number);//报错:非法的前向引用。
       }
    
       private static int number = 10;  //linking之prepare: number = 0 --> initial: 20 --> 10
    
        public static void main(String[] args) {
            System.out.println(ClassInitTest.num);//2
            System.out.println(ClassInitTest.number);//10
        }
    }
    

    另外需要注意的是:

    1. 如果在上述代码里面,number的赋值是在prepare里面就已经初始化为0,然后在initialization里面的<clinit>方法会被根据代码的顺序先被赋值为20,再赋值为10(可以从字节码的角度看得很清晰)。
    2. 静态声明在后面,就不能在前面的静态代码块调用这个变量,会报错:非法的前向引用。
  • 构造器方法中指令按语句在源文件中出现的顺序执行

  • <clinit>()不同于类的构造器。(关联:构造器是就是字节码里面的<init>())

    如果没有静态代码块和类变量的赋值,那么就不会有<clinit>()

  • 若该类具有父类,JVM会保证自身的<clinit>()执行前,父类的<clinit>()已经执行完毕

  • 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁

    比如说:

    public class DeadThreadTest {
        public static void main(String[] args) {
            Runnable r = () -> {
                System.out.println(Thread.currentThread().getName() + "开始");
                DeadThread dead = new DeadThread();
                System.out.println(Thread.currentThread().getName() + "结束");
            };
            Thread t1 = new Thread(r, "线程1");
            Thread t2 = new Thread(r, "线程2");
            t1.start();
            t2.start();
        }
    }
    
    class DeadThread {
        static {
            if (true) {
                System.out.println(Thread.currentThread().getName() + "初始化当前类");
                while (true) {
    
                }
            }
        }
    }
    

    输出的结果是:

    image-20210122213613609

    发现这里只会加载一次,线程1加载了一次静态代码块,线程2不会被执行

2 类加载器

image-20210122214556051
  • JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。

  • 从概念上来讲,自定义类加载器一般只的是程序中由开发人员自定义的一类加载器,但是Java虚拟机规范却没有这么定义,而是讲所有派生于抽象类ClassLoader的类加载器都划分为自定义加载器

  • 无论类加载器的类型如何划分,在程序中我们最场景的类加载器始终只有3个,如下图:

    image-20210122214905443
    1. 引导类加载器
    2. 扩展类加载器
    3. 系统类加载器

    类图如下:

    image-20210122215953883

从代码层面看的话,如下:

image-20210122220213701
image-20210122220321778

在Launcher的类里面有两个内部类AppClassLoader和ExtClassLoader,这两个都都继承了URLClassLoader。

2.1 三种加载器例子

public class ClassLoaderTest {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取其上层:扩展类加载器 ? 
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d

        //获取其上层:获取不到引导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null

        //对于用户自定义类来说:默认使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null
    }
}

疑问:

  1. 明明AppClassLoader和ExtClassLoader是同级的,为啥getParent会得到到ExtClassLoader?

    这个的parent并不是真正的父类,只是一种组合关系,或者说ExtClassLoader是由AppClassLoader进行加载的。

2.2 Bootstrap ClassLoader

image-20210122224051826

2.3 Extension ClassLoader

image-20210122224332766

2.4 AppClassLoader

image-20210122224434954

2.5 三种加载器演示

public class ClassLoaderTest1 {
    public static void main(String[] args) {
        System.out.println("**********启动类加载器**************");
        //获取BootstrapClassLoader能够加载的api的路径
        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL element : urLs) {
            System.out.println(element.toExternalForm());
        }
        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader);

        System.out.println("***********扩展类加载器*************");
        String extDirs = System.getProperty("java.ext.dirs");
        for (String path : extDirs.split(";")) {
            System.out.println(path);
        }

        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d

    }
}

输出以下:

**********启动类加载器**************
file:/D:/leanningPrograms/jdk1_8/jre/lib/resources.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/rt.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/sunrsasign.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/jsse.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/jce.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/charsets.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/jfr.jar
file:/D:/leanningPrograms/jdk1_8/jre/classes
null
***********扩展类加载器*************
D:\leanningPrograms\jdk1_8\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@7ea987ac
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • 博主最近复习深入理解JVM一书,整理归纳,以形成系统认识和方便日后复习。本文主要介绍 类的生命周期 类初始化时机 ...
    stoneyang94阅读 201评论 0 0
  • 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,确实编译语言发展的一大步。 虚拟机把描述类的数据从...
    胡二囧阅读 954评论 0 0
  • 一、类的生命周期 类的生命周期包含下面7个阶段,其中前五步属于类加载阶段: 加载 验证 准备 解析 初始化 使用 ...
    Java_苏先生阅读 324评论 0 0
  • 1、JVM定义 JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机...
    学海一乌鸦阅读 265评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,523评论 16 22