JAVA类加载过程&主动引用和被动引用

1.java类加载过程

重新回顾了java的类的生命周期,主要有:加载、链接、初始化、使用、卸载。上述过程包括了一个java类在jvm虚拟机中声明周期的全过程。
其中,加载、链接、初始化,称为类的加载过程。
而链接又包含了:验证、准备、解析等过程。见下图:


JAVA类加载过程

1.1加载

加载既是将class文件字节码加载到内存中,并将这些静态数据转换为jvm方法区运行时数据结构。在堆中生成一个代表这个类的java.lang.Class对象,作为方法区访问对象的入口。

1.2 链接

将已读入内存的二进制数据合并到JVM运行状态中去的过程。包含验证、准备、解析等过程。
验证:
1.类文件结构检查:确保加载的类信息符合JVM规范,遵从类文件结构的固定格式。
2.语义检查:确保类本身符合Java语言的语法规定,比如验证final类型的类没有子类,以及final类型的方法没有被覆盖。注意,语义检查的错误在编译器编译阶段就会通不过,但是如果有程序员通过非编译的手段生成了类文件,其中有可能会含有语义错误,此时的语义检查主要是防止这种没有编译而生成的class文件引入的错误。
3.字节码验证: 确保字节码流可以被Java虚拟机安全地执行。
字节码流代表Java方法(包括静态方法和实例方法),它是由被称作操作码的单字节指令组成的序列,每一个操作码后都跟着一个或多个操作数。
字节码验证步骤会检查每个操作码是否合法,即是否有着合法的操作数。
4.二进制兼容性验证:确保相互引用的类之间的协调一致。例如,在Worker类的gotoWork()方法中会调用Car类的run()方法,Java虚拟机在验证Worker类时,会检查在方法区内是否存在Car类的run()方法,假如不存在(当Worker类和Car类的版本不兼容就会出现这种问题),就会抛出NoSuchMethodError错误。
准备:
正式为类变量(static变量)分配内存,并设置类变量初始值的阶段。这些内存都将在方法区分配。
解析:
虚拟机常量池内的符号引用替换为直接引用的过程。
例如在Worker类的gotoWork()方法中会引用Car类的run()方法。

public void gotoWork() { 
        car.run();// 这段代码在Worker类的二进制数据中表示为符号引用 
}

在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全名和相关描述符组成。
在解析阶段,Java虚拟机会把这个符号引用替换为一个指针,该指针指向Car类的run()方法在方法区内的内存位置,这个指针就是直接引用

1.3 初始化

初始化是执行类的构造器<clinit>()方法的过程。
类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先触发其父类的初始化。
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
当访问一个java类的静态域时,只有真正声明这个域的类才会被初始化。

*说明 <clinit> 与<init>方法

可能出现在class文件中的两种编译器产生的方法是:实例初始化方法(名为<init>)和类与接口初始化方法(名为<clinit>)。
这两个方法一个是虚拟机在装载一个类初始化的时候调用的(clinit)。另一个是在类实例化时调用的(init)
<clinit>方法:所有的类变量初始化语句和类型的静态初始化语句都被Java编译器收集到了一起,放在一个特殊的方法中。这个方法就是<clinit>
<init>方法:是在一个类进行对象实例化时调用的。实例化一个类有四种途径:调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。Java编译器会为它的每一个类都至少生成一个实例初始化方法。在Class文件中,被称为"<init>"
区别:一个是用于初始化静态的类变量, 一个是初始化实例变量!

1.4 使用

使用既是所需要的对象开始被调用。

1.5 卸载

对象被jvm回收。

示例

package com.dhb.classload;

public class InitDemo {

    static {
        System.out.println("InitDemo static init ...");
    }

    public static void main(String[] args) {
        System.out.println("InitDemo main begin");
        InitA a = new InitA();
        System.out.println(InitA.width);
        InitA b = new InitA();

    }
}

class InitBase{

    static {
        System.out.println("InitBase static init ...");
    }
}

class InitA extends InitBase {

    public static int width = 60;

    static {
        System.out.println("InitA static init ...");
        width = 30;
    }

    public InitA() {
        System.out.println(" InitA init ... ");
    }

}

运行结果:

InitDemo static init ...
InitDemo main begin
InitBase static init ...
InitA static init ...
 InitA init ... 
30
 InitA init ... 

可以看到,在执行结果中,先运行main方法所在类的初始化方法,之后运行main函数。然后运行父类InitBase的初始化方法。之后运行InitA的静态初始化。以及InitA的构造函数。此后虽然new了多个InitA,但是其静态的初始化方法<clinit>只运行了一次。

2.被动引用和主动引用

在java虚拟机规范中,严格规定了,只有对类进行主动引用,才会触发其初始化方法。而除此之外的引用方式称之为被动引用,不会触发类的初始化方法。

2.1主动引用

虚拟机规范规定只有如下四种情况才能触发主动引用:

2.1.1.遇到new、getstatic、setstatic、invokestatic 4条指令时,如果类没有初始化,则需要触发其初始化(final修饰的常量除外)。

(1).使用new关键字实例化对象

package com.dhb.classload;

public class NewClass {

    static {
        System.out.println("NewClass init ...");
    }

}
 class Init1{
    public static void main(String[] args) {
        new NewClass();
    }
}
//输出结果:
NewClass init ...

(2).读取类的静态成员变量

package com.dhb.classload;

public class StaticAttributeClass {

    public static int value = 10;

    public static void staticMethod() {

    }

    static {
        System.out.println("StaticAttributeClass init ...");
    }
}

class Init2{
    public static void main(String[] args) {
        //1.读取静态变量
        int value = StaticAttributeClass.value;
    }
}
//输出结果
StaticAttributeClass init ...

(3).设置类的静态成员变量

class Init2{
    public static void main(String[] args) {
        StaticAttributeClass.value = 5
    }
}
//输出结果
StaticAttributeClass init ...

(4).调用静态方法

class Init2{
    public static void main(String[] args) {
        StaticAttributeClass.staticMethod();
    }
}
//输出结果
StaticAttributeClass init ...
2.1.2.使用java.lang.reflenct包的方法对类进行放射调用,如果没有进行初始化,则需要触发其初始化。
package com.dhb.classload;

public class ReflectClass {

    static {
        System.out.println("ReflectClass init ...");
    }
}
class Init3{
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.dhb.classload.ReflectClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
//输出结果
ReflectClass init ..
2.1.3.当一个类初始化的时候,如果其父类还没有初始化,则需要先对其父类进行初始化。
package com.dhb.classload;

public class SuperClass {
    static {
        System.out.println("SuperClass init ...");
    }
    public static int value = 10;
}

class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init ...");
    }
}

class Init4 {
    public static void main(String[] args) {
        new SubClass();
    }
}
//输出结果
SuperClass init ...
SubClass init ...
2.1.4.当虚拟机启动时,用户需要指定一个执行的主类,虚拟机会首先初始化这个主类
package com.dhb.classload;

public class MainClass {

    static {
        System.out.println("MainClass init ...");
    }

    public static void main(String[] args) {
        System.out.println("main begin ...");
    }
}
//输出结果
MainClass init ...
main begin ...

2.2被动引用

主动引用之外的引用情况都称之为被动引用,这些引用不会进行初始化。

2.2.1.通过子类引用父类的静态字段,不会导致子类初始化
package com.dhb.classload;

public class SuperClass {
    static {
        System.out.println("SuperClass init ...");
    }
    public static int value = 10;
}

class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init ...");
    }

}

class Init4 {
    public static void main(String[] args) {
        int value = SubClass.value;
    }
}
//输出结果
SuperClass init ...
2.2.2.通过数组定义来引用,不会触发此类的初始化
package com.dhb.classload;

public class ArrayClass {

    static {
        System.out.println("ArrayClass init ...");
    }
}
class Init5{
    public static void main(String[] args) {
        ArrayClass[] arrays = new  ArrayClass[10];
    }
}
//输出结果为空
2.2.3.常量在编译阶段会存入调用类的常量池中,本质没有直接引用到定义的常量类中,因此不会触发定义的常量类初始化
package com.dhb.classload;

public class ConstClass {
    static {
        System.out.println("ConstClass init ...");
    }

    public static final int value = 10;
}
class Init6{
    public static void main(String[] args) {
        int value = ConstClass.value;
    }
}
//输出结果为空

2.3练习题

如下类的输出:

package com.dhb.classload;

public class Singleton {

    private static Singleton instance = new Singleton();

    public static int x = 0;
    public static int y;

    private Singleton () {
        x ++;
        y ++;
    }

    public static Singleton getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        Singleton singleton = getInstance();
        System.out.println(x);
        System.out.println(y);
    }
 }

上述类的执行结果为:


执行结果

输出结果竟然是 x为0 y为1 !!!
其实理解了类的加载过程也就不难理解,其过程如下:
(1).执行链接过程,初始化所有的类变量:
instance -> null
x -> 0
y -> 0

(2).执行初始化过程:
new Singleton() 调用构造方法
之后 x -> 1 y -> 1
再执行 x = 0 赋值
最终
x -> 0
y -> 1

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

推荐阅读更多精彩内容