Java类加载机制
类加载器的任务是根据类的全限定名来读取此类的二进制字节流(字节码文件)到 JVM 中,然后转换成一个与目标类对象的java.lang.Class 对象的实例。
类加载的时机
- 隐式加载 new 创建类的实例,
- 显式加载:loaderClass,forName等
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式创建某个类或者接口对象的Class对象。
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
类加载的过程
我们编写的java
文件都是保存着业务逻辑代码。java
编译器将 .java
文件编译成扩展名为 .class
的文件。.class 文件中保存着java转换后,虚拟机将要执行的指令。当需要某个类的时候,java虚拟机会加载 .class 文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程被称为类的加载。
加载
类加载过程的一个阶段,ClassLoader通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个class对象。
验证
目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身的安全,主要包括四种验证:文件格式的验证,元数据的验证,字节码验证,符号引用验证。
准备
为类变量
(static修饰的字段变量)分配内存并且设置该类变量的初始值,(如static int i = 5 这里只是将 i 赋值为0,在初始化的阶段再把 i 赋值为5),这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量(普通的类属性如public int a;)分配初始化,类变量(静态变量)会分配在方法区中,实例变量会随着对象分配到Java堆中。
解析
这里主要的任务是把常量池中的符号引用替换成直接引用
初始化
类的初始化主要是初始化静态成员变量。这里是类记载的最后阶段,如果该类具有父类就进行对父类进行初始化,执行其静态代码块和静态初始化成员变量。(前面已经对static 初始化了默认值,这里我们对它进行赋值)
public class Person{
public static String name="张三";
public static int age;
static{
age=20;
System.out.println("初始化age");
}
public static String address;
static{
address="北京市";
age=34;
}
public static void main(String[] args) {
System.out.println(name);
System.out.println(age);
System.out.println(address);
}
}
当java源代码转换成一个class文件后,其转换成类似下面的代码:
public class Person{
public static String name;
public static int age;
public static String address;
static{
name="张三";
age=20;
System.out.println("初始化age");
address="北京市";
age=34;
}
public static void main(String[] args) {
System.out.println(name);
System.out.println(age);
System.out.println(address);
}
}
forName和loaderClass区别
- Class.forName()得到的class是已经初始化完成的。
- Classloader.loaderClass得到的class是还没有链接(验证,准备,解析三个过程被称为链接)的。