一、oop-Klass类模型
1、MetaSpaceObj类继承关系
2、类的源信息的存储详解
(1)InstanceKlass:存储对象元信息
(2)InstanceMirrorKlass:存储非引用对象的元信息
(3)InstanceRefKlass:存储引用对象的元信息
(4)ArrayKlass:存储数组类的元信息
TypeArrayKlass:基本数组数据类型在JVM中的存在形式(对象为TypeArrayOopDesc)
ObjArrayKlass:引用数组类型数据类型在JVm中的表现形式
二、类的加载过程
0、类加载过程
1、加载的七个步骤
(1)加载阶段:
a、类加载阶段做三件事
(I)通过一个类的权限定名来获取该类的二进制字节流
(II)将这个字节流的静态存储结构转化为方法区的运行时数据结构
(III)在内存堆中生成一个代表该类的java.lang.Class对象,作为该类数据的访问入库
b、类加载的时机:参考《三、Java类的加载》
(2)验证阶段:确保Class文件符合虚拟机要求并且不会威胁到虚拟机安全
a、文件格式验证:验证字节流是否符合Class文件规范,是否能被当前版本的虚拟机处理
b、元数据验证:对字节码描述的信息进行语义分析,确保符合java语言规范
c、字节码验证:通过数据流和控制流分析、确保语义是合法的符合逻辑的
4、符号引用验证:解析阶段发生的验证
(3)准备阶段
a、分配内存:为类的静态变量分配内存
b、赋初始值:为类的成员变量以及被static修饰的成员变量赋初始值;为被final修饰的会直接跳过赋初值而赋原来的值
(4)解析阶段:间接引用转为直接引用
(5)初始化阶段:初始化阶段本质上是执行类构造器<clinit>()的过程,其中生成的静态代码段和定义的顺序保持一致。
a、执行类构造器<clinit>()。类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。当无静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法
b、当初始化一个类的时候,如果发现父类没有被初始化,则需要先触发其父类的初始化。接口类除外,当且仅当父类中定义的变量被使用时,才会触发父类接口的初始化。
c、虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
(6)使用阶段
(7)卸载阶段
三、Java类的加载
1、类的加载分为预加载、懒加载(主动引用和被动引用)
2、预加载
针对String、Integer等类会进行预加载,对于其他的类只在使用的时候加载(懒加载)
3、主动引用:
(1)使用new实例化对象时;
(2)读取和设置类的静态变量、静态非字面值常量(静态字面常量[static final]除外)时;
(3)调用静态方法时;
(4)对类进行反射时;
(5)当初始化一个类时,如果父类没有进行初始化,需要先初始化父类;
(6)启动程序使用了main方法所在的类;
4、被动引用
(1)通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化
(2)定义对象数组和集合时,不会触发该类的初始化;
(3)类A引用类B的static final常量不会导致类B的初始化(注意静态常量必须是字面值常量,否则还是会触发B的初始化的)
(4)通过类名获取Class对象,不会触发类的初始化。如System.out.print(Person.class)
(5)通过Class.forName加载指定的类是,如果指定参数initialize为false时,不会触发类的初始化,否则会触发
(6)通过ClassLoader默认的loadClass方法,也不会触发初始化动作。
注:被顶引用不会导致类的初始化、但不代表类不会经历加载、验证、准备阶段。
5、读取静态变量的底层实现:静态属性是存储在镜像类InstanceMirrorKlass中的
N、其他概念
1、class和klass
class:Java类(Java代码)
klass:Java类在JVM的存在形式(C++代码)
2、动态数据类型和静态数据类型
静态数据类型:JVM中内置的类型,比如八种数据类型
动态数据类型:运行时动态生成。java中的数据类型是动态数据类型
3、直接引用和间接引用
间接引用:指向运行时常量池的引用
直接引用:指向内存地址的引用
4、Java字节码指令:
指令码 | 助记符 | 描述 |
0x00 | nop | 什么也不做 |
0xbb | new | 创建一个对象,并将其引用压入栈顶 |
0xbc | newarray | 创建一个指定原始类型(如:int,char,float...)的数组,并将其引用压入栈顶 |
0xbd | anewarray | 创建一个引用类型(如类、接口、数组)的数组,并将其引用压入栈顶 |
问题:
1、InstanceKlass和InstanceMirrorKlass的区别?答:父类与子类的关系
2、Java中的数组可以用Java代码证明他是动态数据类型,比如用内存?
3、public static final String str = "AAAA";和public static final String str = UUID.randomUUID().toString();的区别,为什么两者所属的类有的打印static代码块中的东西,有的不打印?答:前者属于静态字面值常量,后者属于运行时的常量,会引起类的初始化。
4、类的构造方法init()和类的构造器方法<clinit>()有什么区别?在功能上,类的构造方法主要用于创建类并通过构造方法实现变量的初始化,而<clinit>()主要是初始化成员变量的初始值。两者作用都是初始化,但构造方法初始化的是对象的信息,而<clinit>()初始化的是类的信息。
5、在多线程情况下,JVM如何保证<clinit>()被正确执行且只执行一次?通过锁吗?答:每个InstanceKlass都有一个“Klass _super”指向它的父类,在初始化时,会对此加锁。