Java是一种编译后再经过解释器执行的过程,解释器主要就是如何处理解释Class文件的二进制字节流。
JVM主要包含三大核心部分:运行时数据区,类加载器和执行引擎。
虚拟机的类加载机制:虚拟机将描述类的数据从Class文件加载到内存,并对数据进行校验、准备、解析和初始化,最终就会形成可以被虚拟机使用的Java类型。
一个类的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。
注:加载、验证、准备、初始化和卸载载开始的顺序上时固定的,但是可以交叉进行。
类加载过程:
1.加载(此过程由类加载器实现)
通过一个类的全限定名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在java堆中生成一个代表此类的对象,作为访问方法区这些数据的入口。
2.验证(确保Class文件的字节流中包涵信息复合当前虚拟机要求,不会危害虚拟机自身安全)
文件格式验证:基于字节流验证,验证字节流是否符合Class文件格式的规范,并且能被当前虚拟机处理。
元数据验证:基于方法区的存储结构验证,对字节码描述信息进行语义验证。
字节码验证:基于方法区的存储结构验证,进行数据流和控制流的验证。
符号引用验证:基于 方法区的存储结构验证,发生在解析中,是否可以将符号引用成功解析为直接引用。
3.准备
仅仅为类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值,这里不包含final修饰的static,因为final在编译的时候就会分配,同时这里也不会为实例变量分配初始化。类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
4.解析
解析主要就是将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析。
注:如果有一个同名字段同时出现在一个类的接口和父类中,那么编译器一般都会拒绝变异。
5.初始化
初始化类变量和其他资源,这里执行用户的static字段和静态语句块的赋值操作。这个过程就是执行类构造器<clinit>方法的过程。
<clinit>方法是由编译器收集类中所有类变量的赋值动作和静态语句块的语句生成的,类构造器<clinit>方法与实例构造器<init>方法不同,这里面不用显示的调用父类<clinit>方法,父类的<clinit>方法会自动先执行字类的<clinit>方法。即父类定义的静态语句和静态字段都要优先子类的变量赋值操作。
类加载器
1.类加载器分类:
启动类加载器(Bootstrap ClassLoader):主要负责加载<JAVA_HOME>\lib目录中的,或是-Xbootclasspath参数指定的路径中,并且可以被虚拟机识别的类库到虚拟机内存中。它加载的是System.getProperty("sun.boot.class.path")所指定的路径或jar。
扩展类加载器(Extension ClassLoader):主要负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。它加载的是System.getProperty("java.ext.dirs")所指定的路径或jar。
应用程序类加载器(Application ClassLoader):也叫系统类加载器,主要负责加载ClassPath路径上的类库;如果应用程序没有自定义自己类加载器,则这个就是默认的类加载器。它加载的是System。getProperty("java.class.path")所指定的路径或jar。
2.类加载器的特点:
运行一个程序时,总是由Application ClassLoader开始加载制定的类。
在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载。
Bootstrap Loader是最顶级的类加载器,其父加载器为null
3.类加载器的双亲委派模型
类加载器双亲委派模型的工作过程是:如果一个类加载器收到一个类加载的请求,它首先将这个请求委派给父类加载器去完成,每一个层次类加载器都是如此,则所有的类加请求都会传送到顶层的启动类加载器,只有父加载器无法完成这个加载请求(即它的搜索范围中没有找到所要的类),子类才尝试加载。
使用双亲委派模型主要是两个原因:
a.可以避免重复加载
b.安全因素,如果不用这种模式,则用户可以随意的自定义加载器来替代java核心api,则就会带来安全隐患。