Java虚拟机的类加载
定义:将类的 .class文件中的二进制数据放到内存中,将其放在运行时数据区的方法区中。
一、过程:Java中类加载分为三个过程,分别是加载、连接、解析。
- 加载:查找并加载类的二进制数据
- 连接:分为三个步骤,分别为验证、准备、初始化
- 验证:确保被加载的类的正确性
- 准备:为类的静态变量分配内存,并设置默认值
- 解析:将类中的符号引用(例如一个方法名)转换为直接引用(指针地址等)
- 初始化:为类的静态变量赋予正确的初始值
二、类的加载时机
JVM 规范没有强制约束类加载过程的第一阶段(加载)什么时候开始,但对于“初始化”阶段,有着严格的规定
Java 程序对类的使用分为两种:主动使用和被动使用,其中被动使用不会导致类的初始化。
- 主动使用
- 创建类的实例
- 访问某个类或者某个接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(如Class.forName(“com.test.Test”))
- 初始化一个类的子类
- java虚拟机启动时被表明启动的类
- JDK1.7开始提供的动态语言支持
- 被动使用
除了上面7种方式的主动使用,其他的都是被动使用(O(∩_∩)O哈哈~),也不会导致类的初始化。
一些简单的类加载例子
package com.jvm.dlassLoader;
import java.util.UUID;
/**
*
* 当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中,
* 这是在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被初始化。
*
*/
public class MyTest3 {
public static void main(String[] args) {
System.out.println(MyParent3.str);
}
}
class MyParent3 {
public static final String str = UUID.randomUUID().toString();
static {
System.out.println("MyParent3 static code");
}
}
package com.jvm.dlassLoader;
/**
*
* 对于数组实例来说,其类型是由jvm在运行期动态生成的, [Lcom.jvm.dlassLoader.MyParent4
* 这种形式,动态生成的类型,其父类型就是Object.
*
* 对于数组来说,JavaDoc经常将构成数组的元素为Component,实际上就是将数组降低一个维度后的类型
*
*
* 助记符
* anewarray:表示创建一个引用类型的(如类、接口、数组)数组,并将其引用值压入栈顶
* newarray:表示创建一个指定的原始类型(如int、float、char等)的数组,并将其引用值压入栈顶
*/
public class MyTest4 {
public static void main(String[] args) {
MyParent4 myParent4 = new MyParent4();
System.out.println(myParent4.getClass());
MyParent4[] myParent4s = new MyParent4[1];
System.out.println(myParent4s.getClass());
MyParent4[][] myParent4s1 = new MyParent4[1][1];
System.out.println(myParent4s1.getClass());
System.out.println(myParent4s.getClass().getSuperclass());
System.out.println(myParent4s1.getClass().getSuperclass());
System.out.println("=========");
int[] ints = new int[1];
System.out.println(ints.getClass());
System.out.println(ints.getClass().getSuperclass());
char[] chars = new char[1];
System.out.println(chars.getClass());
boolean[] booleans = new boolean[1];
System.out.println(booleans.getClass());
byte[] bytes = new byte[1];
System.out.println(bytes.getClass());
}
}
class MyParent4 {
static {
System.out.println("MyParent static block");
}
}
package com.jvm.dlassLoader;
/**
* 当一个接口在初始化时,并不要求其父接口都完成了初始化
* 只有在真正使用到父接口的时候(如引用就扣中所定义的常量时),才会初始化
*
*/
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5 {
public static Thread thread = new Thread() {
{
System.out.println("MyParent5 invoked");
}
};
}
class MyChild5 implements MyParent5 {
public static int b = 6;
}