类加载流程:
从上面这幅图可以看出一个类从加载到卸载有7个阶段,其中验证、准备和解析这三个步骤统称为连接。类加载的过程一定会经过加载、验证、准备、初始化和卸载这5个过程,解析和使用则不一定。还有一点要注意的是,并不是每个步骤完全执行完之后才会调用下一个步骤,它会在一个步骤执行的过程中就调用下一个步骤,只是开始的时候一定会按这个顺序。
类加载每个步骤的操作:
一、加载
加载过程会做下面三件事。
- 通过一个类的全限定名来获取定义这个类的二进制字节流。
- 将这个字节流的静态存储结构转化为方法区运行时的数据结构。
- 在内存中为这个类生成一个
java.lang.Class
对象,作为方法区这个类的访问入口。
二、验证
验证是为了确保加载进来的Class
文件的字节流里的内容是符合当前虚拟机的要求,不会危害虚拟机自身的安全,比如一些非法的语句之类的,会进行下面4步验证。
- 文件格式验证,验证字节流是否符合
Class
文件格式的规范,可以被当前虚拟机处理。 - 元数据验证,对加载进来的类的元数据进行语义验证,保证都符合Java语言规范。
- 字节码验证,这一步是在确保元数据符合
Java
语言规范后再验证它是否有非法的,不合逻辑的地方。比如调用一个方法返回int
,却声明一个double
变量去接这个值。 - 符号引用验证,就是对常量池中的各种符号引用进行验证,看这些符号引用是否能被当前类访问到。
三、准备
这个阶段就是给类变量分配内存并且设置初始化值,注意这里只是会分配内存给类变量,实例变量只有在对象初始化的时候才会分配。
四、解析
在这个阶段是把符号引用替换为直接引用的过程,符号引用可以理解为JVM底层一个代表目标对象的常量符号,而直接引用则是真正指向对象的句柄。
五、初始化
初始化是是类加载过程中的最后一步,它会根据代码中的定义再去初始化类变量和其他的资源,到这一步就表示整个类已经初始化完成了。
类加载器:
类加载器执行的操作就是上述加载阶段做的事,通过一个类的全限定名来获取定义这个类的二进制字节流,类加载器可以分为下列三种。
- 启动类加载器,它负责加载存放在
<JAVA_HOME>\lib
目录下或者是被-Xbootclasspath
参数所指定的路径中的类库。 - 扩展类加载器,负责加载存放在
<JAVA_HOME>\lib\ext
目录或者是java.ext.dirs
系统变量所指定的路径中的类库,这个加载器我们可以直接使用。 - 应用程序类加载器,也称为系统类加载器。负责加载用户类路径(ClassPath)上所指定的类库,这个加载器我们也可以直接使用。
双亲委派模型:
双亲委派模式的结构就是上图所示,要求除了最顶层的启动类加载器之外其他的类加载器都要有自己的父类。这里的父类不是用继承实现的,是通过组合方式来复用父类类加载器。它的工作原理是当一个类加载器收到了类加载的请求,不会先自己去加载,而是先委派
给父类加载器去执行,父类加载器如果也有它的父类就继续委派上去,直到最顶层的启动类加载器为止。而当父类没办法加载的时候再一层一层往下返回,让它的子类加载器去加载。
这样的好处在于让类加载器有一种优先级的层级关系。这里要提到一点,判断两个类是否相等,除了要判断是否是同一个Class
文件加载的还需要看是不是同一个类加载器加载出来的。如果不是同一个类加载器加载的,即使是同一个Class
文件加载出来的类也是不相同的。举个例子,比如java.lang.Object
类,当它被加载的时候一定会被委派到最顶层的启动类加载器去加载。所以无论你拿什么类加载器去加载,最后得到的Object
类都是同一个类。如果没有这种委派关系的话,都是Object
类,但是由不一样的类加载器加载的,各自加载出来的Object
类也不相同,会造成一片混乱。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。