读 - 深入理解java虚拟机 - 笔记(五-1) - 虚拟机类加载机制(7章)-类加载时机

本文详细介绍了Java虚拟机中的类加载机制,包括加载、链接、初始化等关键步骤,并深入探讨了类加载的具体时机,以及主动引用与被动引用的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

其实在这篇之前,笔者已经大致研究过虚拟机的类加载机制了,但是不是纯粹读书做的笔记,而是研究的是java的静态变量时读过一次,这一次读书,就是为了再次回顾知识点,加深印象。

java静态变量

首先说到类加载,就需要立即知道三个大步骤:加载,链接,初始化。不过细分之下有以下几点。

1.加载,

2.链接,链接阶段有细分为:验证,准备,解析

3.初始化,

4.使用,

5.卸载,


类加载时机

什么时候需要进行的类的加载,这一点需要明确说明 java虚拟机规范中没有明确规定,这一点由具体的虚拟机自己去把握,但是,但是,但是,这里强调以下初始化操作,虚拟机规范明确指出,5种情况下必须立即对类进行初始化操作(这时候其实之前的操作,比如,加载,链接,等操作自然需要执行)

首先要知道初始化操作分为两大类:

1.主动引用

2.被动引用

1.主动引用

1.遇到new,getstatic,pustatic,invokestatic,这四条字节码时,需要进行类的初始化(如果类没有进行过初始化操作)

   new就是实例化一个对象。

   getstatic,pustatic 就是读取或者设置一个类的静态字段时(这边需要提前明确下,不包括final修饰的静态字段)。

  invokestatic就是调用类的静态方法。

2.反射调用时。

3.当初始化一个类时,如果其父类没有初始化过,则需要进行父类的初始化操作。

4.当执行一个main方法时,需要先进行初始化操作,其实平时说的类的加载,这样说不准确,加载的动作其实是由于初始化操作而引发的。

5.jdk 1.7动态语言支持(不明确)

2.被动引用

主动引用是上面5种场景,其余都是被动引用,其实我有点钻牛角尖,什么样子才叫被动引用,这个名词哪儿来的,书中也没有说,只是说除了上面5种情况,其余的都叫做被动引用。

被动引用例子1,子类访问父类静态字段

/**
 * @author: Wayne
 * @desc: 父类
 * @date: 2017/11/15 11:22
 * @version: 1.0
 */
public class Father {

    public static int num = 1;

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

}
/**
 * @author: wayne
 * @desc: 子类
 * @date: 2017/11/15 11:23
 * @version: 1.0
 */
public class Son extends Father{

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

}
/**
 * @author: wayne
 * @desc: 被动引用-子类直接访问父类静态字段
 * @date: 2017/11/15 11:01
 * @version: 1.0
 */
public class Test {

    public static void main(String[] args) {
        System.out.println(Son.num);
    }
}
father init
1
可以看见输出结果只有父类进行了初始化操作,但是子类没有进行,这种看起来子类应该也会进行初始化操作啊,但是并没有。同理,访问静态方法也是如此。
对于静态字段,只有定义这个字段的类才会被初始化,通过子类引用父类,只会触发父类的初始化,而不会触发子类的初始化。

 
被动引用例子2,通过数组定义来引用类
/**
 * @author: Wayne
 * @desc: 父类
 * @date: 2017/11/15 11:22
 * @version: 1.0
 */
public class Father {

    public static int num = 1;

    public static int get(){
        System.out.println("father get");
        return 1;
    }
    static {
        System.out.println("father init");
    }

}
public class Test {

    public static void main(String[] args) {

        Father []father = new Father[10];

    }
}
没有输出,但是我们发现可以使用father.lethg() 和 father.clone().
被动引用例子3,final修饰的变量fangwen
/**
 * @author: Wayne
 * @desc: 父类
 * @date: 2017/11/15 11:22
 * @version: 1.0
 */
public class Father {

    public final static int NUM = 1;

    public static int get(){
        System.out.println("father get");
        return 1;
    }
    static {
        System.out.println("father init");
    }

}
/**
 * @author: wayne
 * @desc: 被动引用-直接访问final变量
 * @date: 2017/11/15 11:01
 * @version: 1.0
 */
public class Test {

    public static void main(String[] args) {

        System.out.println(Father.NUM);

    }
}
1
只是输出了打印的值1,没有输出Father初始化的过程。这是因为被final修饰的变量在编译期时已经写入了常量池内,所以在Test类去访问时,相当于从常量池内直接去取,不会触发初始化操作了。另外书中介绍存在的常量池优化,会将Father.NUM的值存储到Test的常量池内,后续的继续访问都只是在Test内,与Father无关,Test内并没有Father的符号引用。

另外看见下面两图,上面一个是final类型的,下面一个是普通static变量,通过.访问时,final就能直接看见值,而static并不能。





类加载过程

本文最开始就列出5个过程,现在一一看下。

加载

加载阶段,虚拟机需要完成3个动作,注意,这只是虚拟机规范要求的,具体的实现可以多种多样。

1.通过一个类的全限定名获取定义此类的二进制字节流

2.将这个字节流所代表的静态存储结构转换为方法区运行时数据结构。

3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区内这个类的各种数据的访问入口。(终于看到这句话了,其实我这一次回头看主要就是看这句话的)。

在此,不讨论数组的加载过程,暂时没有兴趣。只讨论一般类的加载阶段。

注意加载阶段,对于一般类来说,静态的东西都放在了方法区内,生成的Class的对象也放在了这里面,作为数据入口。

验证

验证阶段不是我本次读书所关心的,大致列一下吧。以后跟着问题去看。

1.文件格式是否正确,是否符合Class文件规范。

2.元数据验证,对字节码的语义进行分析,是否满足java规范,

3.字节码验证,很复杂,不明。

4.符号引用验证,此验证发生于解析阶段将符号引用转换为直接引用时,主要检查是否能够找到对应的类,并且访问权限等等,常见的异常,比如NoClassFound,NoSuchMethod异常都是由它产生。

准备

准备阶段以前就看过了,最上面java静态的研究时,就知道了,这是一个重要的一块,不知道这个,很多关于静态变量的值的问题,你可能完全懵逼....

这个阶段最重要的就是为类变量分配初始值,这些变量所需要使用的内存都在方法区内进行分配,这边需要注意一个概念,静态变量和实例变量,不知道这两个概念的,先去自行百度,不再叙述,实例变量是随着对象实例化时分配在堆中。这边的初始化值是一个默认初始值的概念,类似于0值,如果感兴趣,可以回到开头去看下那一偏文章内的题目,很有意思。


这边需要注意一下常量的值,也就是final 类型的变量的值,final类型的变量的初始值就在准备阶段赋予了它所指定的值,没有所谓的初始值的概念。

解析

解析阶段不是关注重点,暂时不明和验证一样,知道即可

初始化

在初始化之前的加载阶段,可以允许用户自定义类加载器执行加载,之后的过程都由虚拟机主导,这边我说一说对于初始化阶段的认知吧,就不直接copy书中的观点了。

1.初始化阶段是执行的类变量以及静态快的初始化操作,顺序是你类中定义的顺序

2.初始化操作只执行一次,以后不会再次执行

3.<clinit> 方法与类的构造函数<init>不同的点在于,它不会显示的调用父类的构造方法,想一想这是为什么?还记得类的加载时机不?在子类进行初始化时,必须执行父类的初始化操作,这是虚拟机层面规定的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值