类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则jvm会通过加载、链接、初始化三个过程对类进行初始化
加载
加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
类加载由jvm提供的类加载器来完成,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:
1、从本地文件系统加载。
2、从JAR包加载class文件。
3、通过网络加载class文件。
4、从一个Java源文件动态编译,并执行加载。
链接
当加载过程结束后,系统为之生成了一个Class对象,链接阶段负责把类的二进制数据合并到JRE中。
验证
验证被加载类的内部结构正确性。包括:文件格式验证、元数据验证、字节码验证、符号引用验证。
准备
为类的static变量分配内存,并设置默认值,eg static int a = 0;
解析
讲类的二进制数据中的符号引用替换成直接引用,
符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。
直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
初始化
为类的static变量赋予正确的初始值,eg static int a = 10;
类的加载时机
1、new一个对象。
2、访问某个类或接口的静态变量,或者对该静态变量赋值。
3、调用类的静态方法。
4、反射 Class.forName。
5、初始化一个类的子类。
6、jvm启动时标记的启动类,即文件名和类名相同的那个类。
类的加载器
1、根类加载器:用来加载 Java 的核心类,是用原生代码来实现的,并不继承自java.lang.ClassLoader。负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class。(C++实现)
public static void main(String[] args) { URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for(URL url : urls){ System.out.println(url.toExternalForm()); } }
2、扩展类加载器:负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。(Java语言实现,父类加载器为null)。
3、系统类加载器:负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH路径下所指定的JAR包和类路径。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。
ClassLoader osClassLoader = ClassLoader.getSystemClassLoader();
4、自定义加载器:继承ClassLoader
类加载器加载class大致经过8个步骤
1、检查class是否加载过,即在缓冲区中是否有此class,如果有直接返回对应的java.lang.Class对象。
2、如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。
3、请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。
4、请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。
5、当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。
6、从文件中载入Class,成功后跳至第8步。
7、抛出ClassNotFountException异常。
8、返回对应的java.lang.Class对象。
类加载机制
1、全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
2、双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。优势:可以避免类的重复加载。
3、缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。