一、JVM类加载过程
JVM类加载过程如下图:
JVM类加载过程分为:加载 、链接 、初始化 、使用 、卸载 这五个阶段,其中链接阶段又包括: 验证 、 准备 、 解析 。
- 加载 :通过类的完全限定名,查找此类的二进制字节码文件,通过该字节码文件创建Class对象。
- 链接 :包含 验证 、 准备 、 解析 三个阶段:
- 验证 :确保Class文件复合虚拟机规定的Class文件格式,包含文件格式验证、元数据验证、字节码验证、引用符号验证。
- 准备 :为类的静态变量分配内存并设置初始化值,注:这里不包含
final
修饰的静态变量,因为final
修饰的静态变量是在编译期分配。 - 解析 :将常量池的间接引用转换为直接引用,解析包含字段解析、接口解析、方法解析。
- 初始化 :初始化静态变量和静态块,先初始化父类,再初始化当前类,只有对类主动时才会初始化。
- 使用 :程序代码执行时使用,new出对象程序中使用。
- 卸载 :程序代码退出、异常、结束等,执行垃圾回收。
二、类加载时机
- 创建类的实例,也就是
new
一个对象。 - 访问类的静态方法或者静态变量(包含静态变量赋值)。
- 使用
Class.forName()
反射类。 - 子类初始化的时候。
- JVM启动时标明的启动类。
三、类加载器
类加载器包括启动类加载器、扩展类加载器、系统类加载器、自定义类加载器四种加载器。
-
启动类加载器(Bootstrap ClassLoader):负责加载Java类的核心类,是用原生代码实现。下面代码可以获得启动类加载器所加载的Java核心类库。
URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); for(URL url : urLs){ System.out.println(url.toExternalForm()); }
输出如下:
file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/resources.jar file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/rt.jar file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/sunrsasign.jar file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/jsse.jar file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/jce.jar file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/charsets.jar file:/D:/Develop%20Tools/Java/jdk-8u231/jre/lib/jfr.jar file:/D:/Develop%20Tools/Java/jdk-8u231/jre/classes
扩展类加载器(Extensions ClassLoader):负责加载JRE的扩展目录
lib/ext
或者由java.ext.dirs
系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为Null。-
系统类加载器(System Class Loader):负责在JVM启动时加载来自Java命令的
-classpath
选项、java.class.path
系统属性。可以通过ClassLoader.getSystemClassLoader()
方法获取当前系统类加载器,一般情况是自定义类加载器的父加载器。由Java语言实现,父类加载器为扩展类加载器。System.out.println(ClassLoader.getSystemClassLoader()); System.out.println(ClassLoader.getSystemClassLoader().getParent());
上面代码输出为:
sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@4eb7f003
自定义类加载器(Custom ClassLoader):用户可以通过继承
ClassLoader
类实现自定义类加载器。由Java语言实现,自定义类加载器的父类是系统类加载器。
四、类加载机制
JVM的类加载机制主要有全盘负责、双亲委派、缓存机制三种加载机制。
- 全盘负责:当一个类加载器加载某个Class时,该Class所依赖和引用其他Class也会由该类加载器负责加载,除非指定了使用其他类加载器加载。
- 双亲委派:先让父类加载器加载该Class,只有在父类加载器无法加载该类时才尝试使用自己的类加载器加载。通俗的讲就是在某个特定的类加载器接到加载类请求时,先寄托给父加载器加载,依次递归,如果父加载器可以加载时则成功返回,如果不可以加载就自己去加载。
- 缓存机制:缓存机制会确保所有加载过的Class都被会缓存,当程序中需要某个Class时,类加载器先从缓存区中搜索该Class,只有缓存区中不存在该Class对象时,系统才会读取该类的二进制数据,并将其转换为Class对象,存入缓存区。这就是为什么我们修改Class文件之后,必须重启JVM才会生效的原因。
其中双亲委派机制优势:
- 父类加载器成功加载则返回,子类加载器不会再加载,防止了重复加载。
- 防止核心API库被随意篡改。比如有一个要加载
java.lang.Integer
类的请求,通过双亲委派进制加载传递到启动类加载器,在在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载传递的过来的java.lang.Integer
,而直接返回已加载过的Integer.class
,可以防止核心API被随意篡改。