类的加载过程
加载
加载是指查找并加载类的字节码文件,并将其转换为 JVM 内部的数据结构,即 Class
对象。Java 中的类加载器负责查找类文件,一般有三种类加载器:引导类加载器、扩展类加载器和应用程序类加载器。
在HotSpot中具体是三个步骤:
- 通过类的全限定名获取存储该类的class文件。
- 解析成运行时数据,InstanceKlasss实例。存放在方法区。
- 在堆区生成该类的class对象,InstanceMirrorKlass实例。
程序怎么写都可以。只要满足上面三个效果就OK。改写OpenJdk源码,满足上面三个条件就Ok
加载时机
- 主动使用时
- new, getstatic, putstatic, invokestatic
- 反射
- 初始化一个类的子类去加载其父类。
- 启动类(main函数所在类)
- 当使用JDK1.7动态语言时,如果一个java.lang.invoke.MethodHandler实例最后的解析结果。REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先触发其初始化。
- 预加载:包装类,String, Thread
因为没有指明必须从哪里获取class文件,脑洞大开的工程师开发了这些:
- 从压缩包中读取,jar,war
- 从网络中读取,web applet
- 动态生成,如 动态代理,CGLIB
- 由其他文件生成,如JSP
- 从数据库读取。
- 从加密文件中读取
连接
连接包括验证、准备和解析三个阶段:
验证:验证字节码文件的格式、语义、安全等方面是否符合 JVM 规范。
准备:为类的静态变量分配内存并设置默认初始值。
说明:
实例变量是在创建对象的时候完成赋值的,没有赋初值一说。
int,long,short,char,byte,boolean,float,double,reference
如果被final修饰,在编译的时候会给属性添加ConstantValue属性,准备阶段直接完成赋值,既没有赋初值这一说。
解析:将符号引用转换为直接引用,以便 JVM 快速访问类的方法和变量。
说明: 解析后的信息存储在ConstantPoolCache类实例中
- 类或接口的解析。
- 字段解析
- 方法解析
- 接口方法解析
解析可以发生的时机:
- 加载阶段解析常量池时
- 用的时候
openjdk是第二种思路,在执行特定的字节码指令之前进行解析:
初始化
初始化是指为类的静态变量赋值和执行静态代码块。在 Java 中,类的初始化是在首次使用时进行的,如果该类从未被使用过,则不会进行初始化。
使用
在类初始化完成后,类就可以被使用了。类的使用包括创建对象、调用方法和访问变量等操作。
卸载
卸载是指将类的字节码从内存中移除,并释放相应的资源。类的卸载由 JVM 自动完成,不需要显式调用。
各个阶段会遇到的异常
过程 | 异常类型 | 描述 |
---|---|---|
加载 | ClassNotFoundException | 找不到指定类的定义 |
NoClassDefFoundError | 找不到类的定义文件 | |
UnsupportedClassVersionError | JVM不支持加载的类文件版本 | |
连接 | ClassFormatError | 类文件格式不正确 |
VerifyError | 字节码验证错误 | |
IllegalAccessError | 非法访问错误 | |
初始化 | ExceptionInInitializerError | 静态初始化器抛出异常 |
使用 | NoSuchFieldError | 访问不存在的类成员 |
NoSuchMethodError | 调用不存在的方法 | |
NullPointerException | 访问空对象 | |
卸载 | JVM不支持 |
HotSpot虚拟机中的OOP
类型 | 描述 |
---|---|
Klass | 描述Java类的元数据信息,如类名、访问标志、父类、接口等 |
InstanceKlass | 描述Java类的实例信息,如静态变量、成员变量、方法表等 |
ArrayKlass | 描述Java数组的元数据信息,如数组长度、元素类型等 |
TypeArrayKlass | 描述基本类型数组的元数据信息,如数组长度、元素类型等 |
ObjArrayKlass | 描述对象数组的元数据信息,如数组长度、元素类型等 |
MethodKlass | 描述方法的元数据信息,如方法名、参数类型、返回值类型等 |
ConstantPool | 描述类常量池的信息,如字符串、数字、符号引用等 |
Symbol | 描述字符串的信息,如字符数组、字符串长度、哈希值等 |
ConstantPoolCache | 保存常量池中的一些元素,如字符串、符号引用等的解析结果 |
Klass与InstanceKlass
Klass 对象用于表示类或接口的定义信息,而 instanceKlass 则用于表示类的实例信息。
- Klass
Klass 对象包含了类或接口的元数据信息,例如类名、父类名、接口名、字段信息、方法信息等。Klass 对象还包含了一些与类的实例无关的信息,例如类变量、静态方法等。
- InstanceKlass
instanceKlass 对象则用于表示类的实例信息,例如类的实例变量、实例方法等。instanceKlass 对象与 Klass 对象之间存在继承关系,一个 Klass 对象对应一个或多个 instanceKlass 对象。当类被实例化后,虚拟机会根据 Klass 对象创建一个 instanceKlass 对象,并将该对象的指针存储在实例对象的头部,以便访问该对象的实例变量和方法。
在加载 Java 类时,虚拟机会首先创建 Klass 对象来表示类或接口的定义信息。在连接阶段,虚拟机会为 Klass 对象分配空间,并填充类变量的初始值。在初始化阶段,虚拟机会为类的静态变量赋初值,并执行类的初始化代码。当类被实例化时,虚拟机会根据 Klass 对象创建 instanceKlass 对象,并将该对象的指针存储在实例对象的头部。
一个类实例结构
MetaspaceObj
Metadata
klass
instanceKlass
instanceMirrorKlass描述java.lang.Class实例
instanceRefKlass描述java.lang.Reference的子类
instanceClassLoaderKlass
ArrayKlass
TypeArrayKlass描述java基本类型数组的数据结构
ObjectArrayKlass描述java中引用类型数组的数据结构。
类的元信息是存储在元空间的。
普通的Java类在JVM对应的是instanceKlass的实例。三个类:
- InstanceMirrorKlass:用于表示java.lang.class, Java代码中获取到的class对象,实际上是这个C++类的实例。存储在堆区,学名镜像类。
- InstanceRefClass:用于表示Java.lang.ref.Referece的子类。
- InstanceClassLoader:用于遍历某个加载器加载的类。
Java中的数组不是静态数组类型,是动态数据类型,既是运行时期生成的,Java数组的元信息用ArrayKlass的子类来表示
- TypeArrayKlass:表示基本类型的数组。
- ObjectArrayKlass:表示引用类型的数组。