【JVM之类加载机制1】类加载过程

一、什么是类加载过程

(1)、概述
我们编写的类(.java文件)会被编译器(如javac编译器)编译成Class文件。Java虚拟机把Class文件加载到内存中的过程就称为类加载过程。
(2)、类的生命周期

  • 一个类从被加载到虚拟机内存中,到卸载出内存,共经历七个过程,即这个类的生命周期会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。其中,验证、准备、解析三个阶段又统称为连接。图示如下:
    在这里插入图片描述

    下面我们将逐个介绍类生命周期每个阶段的执行过程。

二、加载阶段

加载阶段是整个类加载过程的第一个阶段。
在本阶段,Java虚拟机主要完成以下三件事:
(1)、 通过一个类的全限定名称获取定义此类的二进制字节流。
(2)、 将该字节流所代表的静态存储结构转化为方法区中数据结构。
(3)、 在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
Java虚拟机运行时数据区示意图如下:

在这里插入图片描述

总结概括就是,先将Class文件以字节流的形式加载到内存,再把这个字节流放到虚拟机运行时数据区的方法区中,以及在内存中生成一个代表该类的Class对象,作为方法区中该类的访问入口。

三、验证阶段

(1)、 验证阶段目的是确保Class文件中的字节流中包含的信息符合虚拟机规范为约束要求,保证被加载类的正确性不会威胁到虚拟机自身的安全。
(2)、 验证的内容有,
文件格式验证:字节流是否符合Class文件格式。
元数据验证:对字节码描述的信息进行语义分析。
字节码验证:确定程序语义是否合法、符合逻辑。
符号引用验证:该类是否缺少或禁止访问它依赖的外部资源,如其他类、方法等。

四、准备阶段

准备阶段是正式为类中定义的静态变量(static修饰的变量)分配内存并设置零值。

零值:虚拟机为基本数据类型设置的初始值,比如int类型的零值为0,boolean类型的零值为false。

比如,private static int a = 123,在准备阶段过后,a的初始值为0,而不是123。而 a = 123这个赋值动作是在初始化阶段进行的。
通常情况下,准备阶段的静态变量的初始值是零值。但是,如果静态变量被 final 修饰,则可能不会是零值。如 private final static int b = 123 ,则 b 的初始值是123,而不是零值。因为 final static修饰的 b 为常量,在编译时就已经被赋值了,即在被编译成字节码的时候就已经是 b = 123;

五、解析阶段

解析阶段是将常量池中的符号引用替换为直接引用的过程。

符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
直接引用:是可以直接指向目标的指针、相对偏移量、或者句柄。

解析阶段主要涉及到符号引用和直接引用的转换,但不影响了解整个类加载过程,故先略过,以后再补充。

六、初始化阶段

初始化时类加载过程的最后一个阶段,在前面的几个类加载动作中,除了加载阶段用户编写的代码(应用程序)可以通过自定义的类加载器参与类加载外,其余阶段的类加载动作都是虚拟机完成的。直到初始化阶段,虚拟机才真正执行类中用户编写的Java代码。

类加载器:前面写到,在加载阶段虚拟机需要完成“通过一个类的全限定名称获取定义此类的二进制字节流”这个动作,虚拟机将这个动作交给应用程序,让其自行去决定怎么获取所需的类。而实现这个动作的代码就被称为类加载器。

初始化阶段就是执行类构造器<clinit>()方法的过程。
<clinit>()方法不是我们直接编写的Java代码,而是javac编译器的自动生成物。
关于<clinit>()方法,需要知道一下几点:
(1)、 此方法由javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而成(即static修饰的变量和static{}),按语句的顺序执行。
如下代码所示:

public class HelloClinit {

    //num = 1 和 static{} 的过程就是 <clinit>()的过程
    private static int num = 1;

    static{
        num = 2;
        a = 2;  //可以为之后定义的变量赋值,但不能访问
        //System.out.println(a);  //报错
    }

    private static int a;

    public static void main(String[] args) {
        System.out.println(num+","+a);
    }
}

输出结果为:2,2

(2)、 < clinit >()方法不需要我们自己去调用父类的< clinit >()方法。因为在子类的< clinit >()执行前,虚拟机会保证父类的< clinit >()已执行完毕
代码如下:

/**
 * Java虚拟机会保证在子类的<clinit>()执行前,父类的<clinit>()先执行,
 * 这也意味着父类中定义的静态代码块要优于子类中的变量赋值操作
 */
public class HelloClinit {
    static class father{
        //父类的静态变量负责语句a = 1和静态代码块static{}就是父类的<clinit>()
        public static int a = 1;
        static {
            a = 2;
        }
    }

    static class son extends father{
        public static int b = 0;
        static {
            b = a;  //故子类在调用的父类的变量a时,实际上a = 2
        }
    }

    public static void main(String[] args) {
        System.out.println(son.b);  //2
    }
}

输出结果为:2

(2)、 Java虚拟机会保证一个类的clinit()方法在多线程环境中被正确的同步加锁,即如果多个线程同时去初始化一个类,那么只有其中一个线程去执行这个类的<clinit>()方法,其他线程都要阻塞的等待,直到活动线程执行完<clinit>()方法,即一个类的clinit()只会被加载一次。

下面的代码模拟一条线程在死循环状态下操作,而另一条线程无限阻塞等待的过程。

public class ClinitThread {
    public static void main(String[] args) {
        /**
         * 下面的代码创建两个线程,让他们都去创建DeadThread类的对象,
         * 但实际上只有线程1会执行<clinit>()方法,即执行static{}语句块,并陷入死循环
         * 而线程2则会阻塞等待
         */
        Runnable r = () ->{
            System.out.println(Thread.currentThread().getName()+"开始");
            DeadThread deadThread = 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);    //陷入死循环
        }
    }
}

以上几个阶段就是类加载的大致过程

七、拓展补充

类加载的时机

  • (1)、在生命周期图中,加载、验证、准备、初始化、卸载。这五个阶段的顺序是确定的,即只有前一个阶段开始,后一个阶段才能开始。但是解析阶段是不确定的,因为它在一些情况下可以在初始化过程之后再开始。注意这个说的是阶段开始,而并不是阶段完成,因为这些阶段通常都是交叉混合进行的。比如在加载阶段已开始但尚未完成时,验证阶段可能已经开始了。
  • (2)、关于什么时候开始类加载的第一阶段加载,具体由虚拟机自身来把握实现,没有明确的规定。但是,对于类加载中的初始化阶段,有且只有以下六种情况必须立即对类进行初始化操作。这也意味着,在初始化操作前,加载,验证、准备也已经开始了。
    具体的六种情况如下:
    ① 遇到new、getstatic、putstatic或invokestatic这四条字节码指令的时候,如果类没有进行初始化,则需要先触发其初始化.
    生成这四条指令的最常见的java代码场景是:

1.使用new关键字实例化对象的时候
2.读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候
3.调用一个类的静态方法的时候

② 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化
③ 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
④ 当虚拟机启动的时候,用户需要制定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类
⑤ 当使用jdk7新加入的动态语言支持的时候,如果一个java,lang.invoke.MethodHandler实例的最后解析结果是REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial四种类的方法句柄,并且这个方法句柄对应的类没有进行过初始化,那么需要先触发其初始化.
⑥ (新)当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果这个接口的实现类发生了初始化,那么该接口要在其之前初始化

以上几种使用类的情况称为类的主动使用,除了以上6种外,其他使用类的方式都称为类的被动使用,即不会导致类的初始化。

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