1、 JVM
- Java Virtual Machine(Java虚拟机),JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
- 引入Java虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
2、追踪类的加载信息
- 【-XX:+TraceClassLoading】(-XX:-表示关闭option选项)
- JVM在当前启动环境下加载类的顺序
- 应用-》运行
- 启动类-》父类-》主类
- 应用-》运行
3、类的加载方式
- 类的加载、连接(验证、准备、解析)、初始化都是在运行时完成的
- 加载时:加载字节码【把二进制的java读入JVM】,并在内存中创建Class对象[唯一]【Class对象封装类在方法区的数据结构,并提供访问方法去内数据结构的接口】
- 准备:为类的静态变量【类变量】分配内存,并初始化为默认值
- 解析阶段:把类中的符号引用(类/方法/字段名)转为直接(内存)引用
- 初始化:为类的静态变量赋(正确)值、执行static
被动
- 除主动外就是被动
主动
- 首次主动加载才会被初始化
1)new
- 普通类直接创建实例
加载过程:
1)连接时(准备)a=0
2)初始化时a=3、再加载静态代码块a=4
3)主函数创建实例,加载代码块a=9,加载构造函数a=18
- 创建子类的实例
当一个类在初始化时,要求其父类都已经被初始化完毕。
加载过程:
1)连接时(准备)a=0,c=0
2)初始化时a=3、c=3、再加载静态代码块a=4、c=6
3)创建子类实例,先加载父类代码块a=9、父类构造函数a=18、再加载子类代码块c=11、子类构造函数c=33
2)访问类的静态变量/赋值、调用类的静态方法
- 直接访问普通类的静态变量
加载过程:
1)连接时(准备)a=0
2)初始化时a=3,加载静态代码块a=4
- 调用普通类的静态方法,同上初始化+执行调用方法
3)反射
-
1、含main()类
-
2、普通类
-
3、子类
4)初始化一个类的子类
- 子类调用父类的静态方法,父类初始化,子类并不会被初始化
- 子类调用自己的静态变量才会被初始化
5)JVM启动时被标记为启动类的类
6)MethodHandle【空】
4、测试例子
实例在最前面
加载过程:
1)连接时(准备)创建实例、a=0
2)初始化时加载代码块a=5、加载构造函数a=10,进行初始化赋值a=3、并加载静态代码块a=4
主函数再创建一个实例:
创建实例放声明和静态代码块中间
加载过程:
1)连接时(准备)a=0、创建实例
2)初始化时a=3;加载代码块a=8,加载构造函数a=16,加载静态代码块a=17
加载过程:
1)连接时(准备)a=0,创建实例
2)初始化时a=3,加载静态代码块a=4,加载代码块a=9,加载构造函数a=18
5、常量与类的初始化
1)编译期能确定常量
- 在编译阶段会存入调用这个常量的方法的类的常量池中
- 本质上,调用类,并没有直接引用到定义类的常量,因此,不会初始化定义常量的类
- 即:A中aa存放到testAB的常量池中
示例
测试 -------删除A的字节码
反编译:
- 补充:还有iconst_0,iconst_m1(minus1/-1)
常量aa=100时,变bipush
- 另:反编译的用法:
2)当常量值在运行期才能确定时
- 删除定义常量的类的字节码文件
证明:当一个常量的值在编译期无法确定运行期才能确定时,它不会被放到调用此常量的类的常量池中,而是主动使用此常量被定义的类(定义此常量的类会被初始化)
6、数组与类的初始化
- 数组的类型是JVM在运行期动态生成的,表示为[L…
- 动态生成的类型,其父类就是Object,故A没有初始化
7、接口与初始化
- ps:接口默认final
- 当一个接口在初始化的时候,父接口不一定被初始化
- 只有当真正使用父接口时,才会被初始化
7.1)引用子接口常量【未】
子接口和父接口都没有被初始化,子接口常量被放入调用该常量的类的常量池中
1)
2)子接口和父接口都删掉
7.2)引用父接口常量【未】
1)
2)删除子接口字节码
7.3)父接口中常量值不确定时,引用父接口常量【子、父接口皆初始化】
1)删除父接口
2)删除子接口
7.4)父接口中常量值不确定时,引用子接口常量【未】
1)
删除父接口
2)删除子接口
7.5)子、父接口中常量值不确定时,引用子接口常量【子、父初始化】
1)删除父接口
2、删除子接口
7.6)子、父接口中常量值不确定时,引用父接口常量【子、父初始化】
。。。