1.概述java
a.JVM类加载机制:是虚拟机把描述类的数据从Class
文件加载到内存,并对数据进行校验、转换解析和初始化,最终造成可被虚拟机直接使用的Java
类型的过程。数据库
b.特性:运行期类加载。即在Java语言里面,类型的加载、链接和初始化过程都是在程序运行期完成的,从而经过牺牲一些性能开销来换取Java程序的高度灵活性。数组
JVM运行期动态加载+动态链接->Java语言的动态扩展特性安全
2.类加载全过程网络
类从被加载到虚拟机内存中开始、到卸载出内存为止,整个生命周期包括7阶段:数据结构
其中,验证、准备、解析这3个部分统称为链接(Linking),流程以下图:多线程
注意:布局
- 『加载』->『验证』->『准备』->『初始化』->『卸载』这5个阶段的顺序是肯定的,而『解析』可能为了支持Java语言的运行时绑定会在『初始化』后才开始。
- 上述阶段一般都是互相交叉地混合式进行的,好比会在一个阶段执行的过程当中调用、激活另一个阶段。
接下来将分别介绍上述几个阶段。性能
a.加载spa
任务:
- 经过类的全限定名来获取定义此类的二进制字节流。如从ZIP包读取、从网络中获取、经过运行时计算生成、由其余文件生成、从数据库中读取等等途径......
- 将该二进制字节流所表明的静态存储结构转化为方法区的运行时数据结构,该数据存储数据结构由虚拟机实现自行定义。
- 在内存中生成一个表明这个类的
java.lang.Class
对象,它将做为程序访问方法区中的这些类型数据的外部接口。
b.验证
因而可知,它能直接决定JVM可否承受恶意代码的攻击,所以验证阶段颇有必要,但因为它对程序运行期没有影响,并不必定必要,能够考虑使用
-Xverify:none
参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
java.lang.IncompatibleClassChangeError
异常的子类。c.准备
任务:
- 为类变量分配内存:由于这里的变量是由方法区分配内存的,因此仅包括类变量而不包括实例变量,后者将会在对象实例化时随着对象一块儿分配在Java堆中。
- 设置类变量初始值:一般状况下零值。
d.解析
CONSTANT_Class_info
)CONSTANT_Fieldref_info
)CONSTANT_Methodref_info
)CONSTANT_InterfaceMethodref_info
)CONSTANT_MethodType_info
)CONSTANT_MethodHandle_info
)CONSTANT_InvokeDynamic_info
)举个例子,设当前代码所处的为类D,把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,解析过程分三步:
- 若C不是数组类型:JVM将会把表明N的全限定名传递给D类加载器去加载这个类C。在加载过程当中,因为元数据验证、字节码验证的须要,又可能触发其余相关类的加载动做。一旦这个加载过程出现了任何异常,解析过程就宣告失败。
- 若C是数组类型且数组元素类型为对象:JVM也会按照上述规则加载数组元素类型。
- 若上述步骤无任何异常:此时C在JVM中已成为一个有效的类或接口,但在解析完成前还需进行符号引用验证,来确认D是否具有对C的访问权限。若是发现不具有访问权限,将抛出
java.lang.IllegalAccessError
异常。
e.初始化
<clinit>()
的过程。
<clinit>()
:由编译器自动收集类中的全部类变量的赋值动做和静态语句块static{}
中的语句合并产生。
- 是线程安全的,在多线程环境中被正确地加锁、同步。
- 对于类或接口来讲是非必需的,若是一个类中没有静态语句块,也没有对变量的赋值操做,那么编译器能够不为这个类生成
<clinit>()
。- 接口与类不一样的是,执行接口的
<clinit>()
不须要先执行父接口的<clinit>()
,只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也同样不会执行接口的<clinit>()
。
new
、getstatic
、putstatic
或invokestatic
这4条字节码指令时;java.lang.reflect
包的方法对类进行反射调用的时候;java.lang.invoke.MethodHandle
实例最后的解析结果为REF_getStatic
、REF_putStatic
、REF_invokeStatic
的方法句柄,且这个方法句柄所对应的类未进行初始化,需先触发其初始化。3.类加载器&双亲委派模型
每一个类加载器都拥有一个独立的类名称空间,它不只用于加载类,还和这个类自己一块儿做为在JVM中的惟一标识。因此比较两个类是否相等,只要看它们是否由同一个类加载器加载,即便它们来源于同一个Class文件且被同一个JVM加载,只要加载它们的类加载器不一样,这两个类就一定不相等。
a.类加载器
从JVM的角度,可将类加载器分为两种:
C++
语言实现,是虚拟机自身的一部分。<JAVA_HOME>\lib
目录中、或被-Xbootclasspath
参数所指定路径中的、且可被虚拟机识别的类库。Java
语言实现,独立于虚拟机外部,而且全都继承自抽象类java.lang.ClassLoader
,可被Java程序直接引用。常见几种:
sun.misc.Launcher$ExtClassLoader
实现。<JAVA_HOME>\lib\ext
目录中的、或者被java.ext.dirs
系统变量所指定的路径中的全部类库。ClassLoader#getSystemClassLoader()
的返回值,故又称为系统类加载器。sun.misc.Launcher$App-ClassLoader
实现。上述几种类加载器的关系如图:
须要注意的是,虽然数组类不经过类加载器建立而是由JVM直接建立的,但仍与类加载器有密切关系,由于数组类的元素类型最终还要靠类加载器去建立。
b.双亲委派模型(Parents Delegation Model)
java.lang.ClassLoader的loadClass()
中。好比,某些类加载器要加载
java.lang.Object
类,最终都会委派给最顶端的启动类加载器去加载,这样Object
类在程序的各类类加载器环境中都是同一个类。相反,系统中将会出现多个不一样的Object类,Java类型体系中最基础的行为也就没法保证,应用程序也将会变得一片混乱。