类的加载、连接、初始化

转载、引用请标明出处
https://www.jianshu.com/p/853701433b3a
本文出自zhh_happig的简书博客,谢谢

以下内容,是本人学习笔记和工作中的总结,仅供大家参考,有误的地方还请指正

java虚拟机

执行一个java程序,都会启动一个java虚拟机的进程,进程里面包含一个主线程来执行程序,当程序执行完了之后,java虚拟机进程就消亡了。
在如下几种情况下,java虚拟机将结束生命周期

  • 执行了System.exit()方法
  • 程序正常执行结束
  • 程序在执行过程中遇到了异常或错误而异常终止
  • 由于操作系统出现错误而导致java虚拟机进程终止

一 类的加载、连接、初始化

1 加载:查找并加载类的二进制数据

  • 类记载器ClassLoader将java的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区中
  • 加载后,虚拟机在堆区创建一个与该类对应java.lang.Class对象,不管类的对象有多少个,与此类对应的Class对象只有一个,Class对象用来封装类在方法区内的数据结构,所以类里面的内容都可以通过Class对象获取。Class对象是反射的入口。
  • 类的加载并不是你首次使用的时候去加载,而是预料到某个类要被使用的时候预先加载它。
  • 程序只有被加载到内存中,才能被执行

2 连接

  • 验证:确保被加载的类的正确性
    • 通过javac生成的.class文件肯定是正确的,但是有的直接手动生成.calss可能是不符合java字节码规则的,这里要验证。
  • 准备:为类的静态变量分配内存,并将其初始化为默认值
    • 到这一步,类的加载、连接、初始化还没有完成,不会生成对象,所以所有的实例对象都不会分配内存,只有静态变量会被分配内存,初始化为默认值:int型初始化为0,boolean类型初始化为false,引用类型初始化为null。顺序:从上至下。
  • 解析:把类中的符号引用转换为直接引用
    • 在Worker类中调用car.run(),Car类的run()方法。car.run()---这是符号引用,在解析阶段java虚拟机会把这个符号引用替换为一个指针,该指针指向了Car类的run()方法在方法区内的内存位置,这个指针就是直接引用。

3 初始化:为类的静态变量赋予正确的初始值

  • 正确的初始值是在程序中被赋的值,比如 int a = 3,这一步将3赋值给a;所以a经过了两次赋值:第一次是连接准备阶段,a先会被赋默认初始值0,第二就是初始化为程序中赋的值。
  • 静态变量的初始化有两种方式
    • 在静态变量的声明处进项初始化
    • 在静态代码块中进行初始化
  • 初始化步骤
    • 假如这个类还没有被加载和连接,先进行加载和连接
    • 假如这个类存在父类,父类还没有初始化,先初始化直接的父类
    • 初始化语句执行顺序:从上至下依次执行
  • 初始化时机
    • 类的主动使用
    • 当java虚拟机初始化类时,它所有的父类都已经被初始化了,但是这条规则不适用与接口
    • 在初始化一个类时,并不会先初始化它实现的接口
      • 初始化一个接口时,并不会初始化它的父接口
      • 只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化

二 java程序对类的使用方式

主动使用
被动使用

主动使用(6种),除了以下6种,其他类的使用全是被动使用

  • 创建类的实例
    • new出来一个类的实例对象,即为对这个类的主动使用
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
    • int b = Test.a; 这个也是对Test类的主动使用
    • final修饰的静态变量要注意:如果这个变量在编译时就能确定它的值,就不会导致类被初始化,例如 public static final int a = 6/3
    • 如果这个变量要在运行时才能确定它的值,才会导致类被初始化,例如public static final int a = new Random().nextInt(100)
  • 调用类的静态方法
    • int b = Test.add()
    • 只有当访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类和接口的主动使用,详见练习题5中例子。
  • 反射创建类的实例
  • 初始化一个类的子类
    • 初始化一个父类的子类,也是对这个父类的主动使用
  • Java虚拟机启动时被标注为启动的类
    • 启动程序的类,包含main方法的类

所有的java虚拟机实现必须在每个类或接口被java程序 首次主动使用 时才初始化他们,换句话说就是以上6种情况,而且是第1次主动使用才会在初始化。其他情况,如被动使用,或第二次主动使用,都不会执行类的初始化。

调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。调用ClassLoader类的loadClass方法只是执行了加载操作。

二 示例演示——增加理解

示例1

class Singleton{

    private static Singleton singleton = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

    private Singleton(){
        counter1 ++;
        counter2 ++;
    }

    public static Singleton getInstance(){
        return singleton;
    }

}

public class JVMTest {

    public static void main(String[] args){
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1 = " + singleton.counter1);
        System.out.println("counter2 = " + singleton.counter2);
    }

}

输出结果
1
0

在类的连接--准备阶段,singleton被赋默认值null,counter1和counter2被赋默认值0;初始化的时候,先初始化singleton,创建实例,在Singleton 构造方法中counter1++,counter2++后,counter1和counter2变成了1,然后在再初始化counter1和counter2,counter1没有被赋值,所以还是1, counter2被赋值了0,所以counter2最终为0。

如果将上面的部分代码顺序改一下:

public static int counter1;
public static int counter2 = 0;
private static Singleton singleton = new Singleton();
输出结果
1
1
如果看懂了上面的代码,这个不难理解。

示例2

public class Test2 {

    public static void main(String[] args){
        System.out.println(FinalTest.x);
    }

}

class FinalTest{

    public final static int x = 1/3;
    static{
        System.out.println("FinalTest static block");
    }

}

输出结果
0

final修饰的静态变量要注意:如果这个变量在编译时就能确定它的值,就不会导致类被初始化,例如
public static final int x = 1/3

public class Test2 {

    public static void main(String[] args){
        System.out.println(FinalTest.x);
    }


}

class FinalTest{

    public final static int x = new Random().nextInt(100);
    static{
        System.out.println("FinalTest static block");
    }

}

输出结果
FinalTest static block
75

如果这个变量要在运行时才能确定它的值,才会导致类被初始化,例如
public static final int x = new Random().nextInt(100)

示例3

public class Test3 {

    static {
        System.out.println("Test3 static block");
    }

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

}

class Parent{

    static int a = 3;
    static{
        System.out.println("Parent static block");
    }

}


class Child extends Parent{

    static int b = 4;
    static{
        System.out.println("Child static block");
    }

}

输出结果?
Test3 static block
Parent static block
Child static block
4

Test3是程序入口类,最先被加载初始化;先加载初始化父类,再子类。

示例4

public class Test4 {

    static {
        System.out.println("Test4 static block");
    }

    public static void main(String[] args){
        Parent2 parent2;
        System.out.println("-------------");

        parent2 = new Parent2();
        System.out.println(Parent2.a);
        System.out.println(Child2.b);
    }

}

class Parent2{

    static int a = 3;
    static{
        System.out.println("Parent2 static block");
    }

}


class Child2 extends Parent2{

    static int b = 4;
    static{
        System.out.println("Child2 static block");
    }

}

输出结果
Test4 static block
-------------
Parent2 static block
3
Child2 static block
4

Child2、Parent2是由同一个类加载器加载的,所以Parent2初始化了, Child2初始化的时候就不会再去初始化父类了。
PS: 如果有两个加载器:A类的加载器,B类的加载器,AB不是父子关系,即使A类的加载器已经初始化了Child2类,在B类的加载中还是可以再去初始化Child2类的。详解请看后续的 Java类加载器 文章

为什么下面这一行代码不去初始化Parent2呢?

...
Parent2 parent2;
...

因为这只是声明了一个变量,并没有主动使用类,所以不会初始化。

示例5

public class Test5 {

    public static void main(String[] args){
        System.out.println(Child3.a);
        Child3.doSomething();
    }

}

class Parent3{

    static int a = 3;
    static{
        System.out.println("Parent3 static block");
    }

    static void doSomething(){
        System.out.println("doSomething");
    }

}


class Child3 extends Parent3{

    static{
        System.out.println("Child3 static block");
    }

}

输出结果?
Parent3 static block
3
doSomething

Child3.a,Child3.doSomething() 为什么Child3没有别初始化?
只有当访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类和接口的主动使用。而Child3.a,Child3.doSomething()调用的静态变量或静态方法不是在Child2类中定义的,而是在父类中定义的,所以不会对Child2进行初始化。

示例6

class C{

    static {
        System.out.println("class C");
    }

}

public class Test1 {

    public static void main(String[] args) throws Exception{
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class<?> clazz = loader.loadClass("com.zhh.jvm.loadClass.C");//加载 C 类
        System.out.println("------------");
        clazz = Class.forName("com.zhh.jvm.loadClass.C");
    }

}

输出结果?
------------
class C

loader.loadClass("com.zhh.jvm.loadClass.C");
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。调用ClassLoader类的loadClass方法只是执行了加载操作。clazz = Class.forName("com.zhh.jvm.loadClass.C");是反射创建类的实例,是类的主动使用,所以导致类被初始化。

以上内容,是本人学习笔记和工作中的总结,仅供大家参考,有误的地方还请指正

转载、引用请标明出处
https://www.jianshu.com/p/853701433b3a
本文出自zhh_happig的简书博客,谢谢

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