一、类加载概念
我们知道,在java中,我们的java代码在编译之后最终会以class文件的形式存在。jvm就是通过加载class文件来执行程序。
类加载的过程如下,首先jvm虚拟机读取class二进制文件,将数据读取到数据区的方法区内(todo jvm内存模型),然后在堆区创建一个java.lang.Class的对象(没错,类本质上也对应着一个对象),用来统一封装堆数据区的类结构,提供统一的访问方式和接口。
二、类加载流程
class文件在jvm的生命周期包含如下几个关键流程:

加载
加载主要做了以下几件事:
- 通过全限定类名定位到class文件
- 读取二进制文件,将数据装载到方法区
- 在堆区生成一个对应的java.lang.Class 对象,对外提供访问。
链接
-
验证
验证主要是对文件数据格式的正确性等进行校验操作。
-
准备
为类的静态成员变量分配内存空间及默认初始值,该初始值是类型的默认值,而非代码中显式指定的值。(显式指定的值在初始化阶段才会被处理)
-
解析
将类中的符号引用替换为直接引用。
初始化
- 类变量的初始值设定。
- 执行静态代码块。
- 初始化时机:使用该类的时候
1.Class.forName的方式加载类实例。
2.访问类的静态变量、方法,或者访问类的静态方法。
3.初始化该类的子类(子类初始化时,会先触发父类初始化完毕)
4.jvm表明为启动类的类 - 初始化步骤
- 如果类未加载,先进行加载和链接。
- 如果有父类并且未初始化,执行父类的初始化。
- 按顺序执行类的静态成员、静态方法初始化。
三、类加载方式
java类加载有两种方式
ClassLoader.loadClass
这种方式只会执行加载和链接过程,不会对类进行初始化
Class.forName
这种方式在加载链接之后,还会对类执行初始化。当然也可以通过Class.forName(String name, boolean initialize, ClassLoader loader)
决定是否执行初始化环节。
四、双亲委派模型
jvm的加载器整体如下图,其中最重要的特性就是双亲委派
。所谓的双亲委派其实就是,当底层的APPClassLoader需要加载一个类时,会首先向上层的ExtClassLoader询问是否已经加载相关类,而ExtClassLoader会进一步询问BootStrapLoader是否已加载所需类。通过这种方式,可以保证整个JVM里一个类只被加载一次。

五、常见问题
-
ClassNotFoundException
和NoClassDefFoundError
有什么区别?
首先,这两个一个是异常Exception一个是错误Error。
ClassNotFoundException
是指我们通过上文中提到类加载方式,手动进行加载,如果没有找到,则会抛出ClassNotFoundException
,是一个程序代码异常
。
ClassLoader.getSystemClassLoader().loadClass("notExistClass");
而如果jvm运行时,去实例化或者使用一个类时,发现类不存在,则会抛出Error。例如我们程序中引入一个Ajar包,而A中依赖B类,B类对应的Jar包我们没有引入,那么当我们使用A类时,jvm会尝试去加载B类,进而会抛出NoClassDefFoundError
。
- 双亲委派模式是否可以被破坏?
参考文献
https://zhuanlan.zhihu.com/p/25228545
https://juejin.im/post/5cfdeea2e51d454d1d6284e7
网友评论