转自:java类的加载机制
JVM加载类的逻辑过程
三大阶段:加载、连接、初始化
连接阶段:验证、准备、解析
加载
根据类的 全路径限定名称 ,找到类的 二进制字节流文件。
根据类的 二进制字节流文件 ,生成类的 运行时数据结构,存储在类的元数据区-MetaSpace(在jdk1.8之前,类的运行时数据结构被存储在MethodArea也即PermGen中)。
生成类的 java.lang.Class对象,存储在jvm 堆中。
连接·验证
类的二进制字节流文件格式验证:是否符合.class文件格式的规范,如主次版本号、常量类型等。
元数据验证、字节码验证、符号引用验证
连接·准备
为类的静态变量分配内存,并将其初始化为默认值(0, null, false)。
例外:对于定义为 final static 的静态变量,此时就会被赋值为代码中指定的值。
如 private final static int a=3; 准备阶段完成后,a的值为3。
连接·解析
把类中的符号引用转换为直接引用。
将 :类、类字段、类方法、接口、接口方法、方法类型、方法句柄、和调用点限定符,以上7种符号引用,
替换为 : 直接指向目标的指针、相对偏移量、或者一个间接定位到目标的句柄。
如:将 class MyClass {} 中的 MyClass 替换为 MyClass 这个类的java.lang.Class实例对象在堆中的实际地址?
初始化(主动使用类的时候,才会进行初始化)
对静态变量赋值、执行静态代码块,按照代码声明的顺序执行。
JVM可能根据预判,自动对未使用到的类进行加载、连接,但是不会进行初始化。
只有当主动使用类时,才会对类进行初始化。
主动使用类test.User,类初始化的6种情况
new User();
User.staticVar; //使用静态变量,final类型的除外
User.staticMethod(); //调用静态方法
Class.forName(“test.User”); //反射
初始化子类时,首先会初始化父类。 //但是如果通过子类引用父类的变量或者方法时,子类不会被初始化。另,new MyClass[10];数组时不会初始化
JVM启动时,被标明为启动类的类。
类初始化机制
当某个类被主动使用时,要对其进行初始化
- 先初始化其父类
- 如果类自身还未被加载,则对其加载和连接
- 最后顺序执行类中的初始化语句,即:对static变量赋值,执行static代码块等
代码层面的类加载
Class.forName(“test.User”); // 加载、连接、初始化
ClassLoader.loadClass(“test.User”); // 只加载、连接,不初始化
类加载器的运行机制
- 全盘负责
当一个类加载器负责加载某Class时,该类所依赖和引用的其它所有Class也将由此类加载器负责加载,除非显式地使用另一个类加载器来载入 - 父类委托
先委托父类加载器对类进行加载,只有当父类加载器无法加载该类时,才在自己的类路径中加载该类 - 缓存机制
会将所有加载过的Class缓存,当使用某Class时,先从缓存中寻找该Class。只有当缓存中不存在该Class时,才会加载Class的二进制字节码文件,生成Class对象,并缓存。
因此修改了Class后,必须重启JVM,程序的修改才会生效。
Jdk自带的类加载器
- Bootstrap ClassLoader:
负责加载jre\lib下,或-Xbootclasspath参数指定的路径下的.jar、java.*等类文件(如java.lang.String) - ExtClassLoader:
负责加载jre\lib\ext下,或java.ext.dirs系统变量指定的路径下的所有.jar、javax.*等类文件(如javax.servlet)
开发者可以直接使用该类加载器 - AppClassLoader:
负责加载用户类路径(ClassPath)所指定的类
开发者可以直接使用该类加载器
自定义类加载器
一般只重写findClass()方法,用于实现网络方式获取Class的二进制字节流。示例:
package com.neo.classloader;
import java.io.*;
public class MyClassLoader extends ClassLoader {
private String root;
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) {
String fileName = root + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
try {
InputStream ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public String getRoot() {
return root;
}
public void setRoot(String root) {
this.root = root;
}
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
classLoader.setRoot("E:\\temp");
Class<?> testClass = null;
try {
testClass = classLoader.loadClass("com.neo.classloader.Test2");
Object object = testClass.newInstance();
System.out.println(object.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}