Java类加载机制
Java类加载过程
一般我们把Java的类加载分为三个过程: 加载、连接、初始化
加载 Loading
Java将字节码数据从不同的数据源读取到JVM中,并映射为JVM认可的Class对象。这里的数据源可以是jar文件,class文件,网络数据等。如果数据源类型不是ClassFile的结构,则会抛出ClassFormatError。
我们可以自定义类加载器来实现类加载的过程
链接 Linking
可以分为3步。
-
验证 Verification
为了保证虚拟机的安全,JVM需要检验字节信息是否符合Java虚拟机规范,否则会认为是VerifyError。
-
准备 Preparation
创建类和接口中的静态变量,并初始化静态变量的初始值,非配所需要的内存空间。不会进一步执行JVM指令。
-
解析 Resolution
将常量池中的符号引用symbolic reference替换为直接引用。
初始化 Initializa
执行类初始化的代码逻辑,包括静态字段赋值,执行类中静态代码块的逻辑。
在编译阶段会把这部分的逻辑整理好,父类型初始化逻辑要优先于当前类型。
什么是双亲委派模型
当类加载器试图加载某个类时,如果每个类都自己加载需要的类型,会出现重复加载的情况,因此除非父类加载器找不到相应的类型,否则尽量将这个任务代理给当前加载器的父加载器去做。
这样可以避免加载重复类型,减少资源浪费。
准备阶段给静态变量赋值,那么普通静态变量,静态常量(原始类型和引用类型)在加载时有什么区别?
public class CLPerparation {
public static int a = 10;
public static final int INT_CONSTANT = 100;
public static final Integer INTEGER_CONSTAN = Integer.valueOf(1000);
}
编译和反编译一下
Javac CLPreparation.java
Javap –v CLPreparation.class
0: bipush 10
2: putstatic #2 // Field a:I
5: sipush 1000
8: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: putstatic #4 // Field INTEGER_CONSTAN:Ljava/lang/Integer;
可以看到普通原始类型静态变量引用类型常量,需要额外调用putstatic指令,而原始类型常量不需要。
内建的类加载器有哪些?
以JDK8为例:
jdk8类加载机制.png-
引导类加载器 Bootstrap Class-Loader
负责加载jre/lib 下面的jar包,如rt.jar 用原始C++来实现,并不是继承java.lang.ClassLoader
public final ClassLoader getParent()
可以通过该方法获取父类的加载器,在扩展类加载器中返回null
-
扩展类加载器 Extension or Ext Class-Loader
负责加载放在jre/lib/ext下的jar包,这就是extension机制。
可以通过
java.ext.dirs
覆盖java -Djava.ext.dirs=your_ext_dir HelloWorld
-
应用类加载器 Application or App Class-Loader
加载classpath中的内容
系统类加载器 System Class-Loader 是JDK默认的应用类加载器,可以被修改
java -Djava.system.class.loader=com.yourpkg.YourClassLoader HelloWorld
类加载机制有三个基本特征: -
双亲委派模型 parent delegation model
不是每个类加载都遵循这个机制。
有的时候可能出现加载用户代码的情况。比如JDK中的ServiceProvider/ServiceLoader机制,用户可以在标准API基础上提供自己的实现类,典型的有JDBC,JNDI,Cipher,文件系统等,这些用到的就是所谓的上下文加载器。
-
可见性
子类加载器可以访问父类加载器的加载类型,但是反过来不可以,不然会因为缺少隔离性,无法用类加载器实现容器的逻辑
-
单一性
由于父类的加载器类型对子类可见,所以父类加载过放类型,在子类中就不会再次加载。
感谢杨晓峰老师
自定义类加载器如何定义?
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader2 extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] b = loadClassData(name);
return defineClass(null, b, 0, b.length);
}
private byte[] loadClassData(String name) {
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
System.out.println(name);
try {
inputStream = new FileInputStream(new File(name));
byteArrayOutputStream = new ByteArrayOutputStream();
int len = 0;
byte[] b = new byte[1024];
while ((len = inputStream.read(b)) != -1) {
byteArrayOutputStream.write(b, 0, len);
}
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
测试
package classloader;
public class MyTest {
public static void main(String[] args) {
String pathname = "文件路径";
MyClassLoader2 classLoader = new MyClassLoader2();
try {
Class<?> object = classLoader.loadClass(pathname);
System.out.println(object.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Java程序启动慢,类加载器的开销如何减小?