要成为优秀的Java开发人员,需要深入了解Java平台的工作方式,其中类加载机制和JVM字节码这样的核心特性需要我们多下功夫去进行一定的深入了解。我们比较熟悉的是classloader这个关键字,顾名思义就是我们常见的类加载器,它的作用就是将编译后的.class文件加载到内存中去,在具体了解ClassLoader之前我们需要了解JVM的类加载机制。
1.类加载机制
代码编译后,会生成JVM能够识别的二进制字节流文件(.class文件),而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验,准备,解析,初始化。使
这些数据能够转换为可以被JVM直接使用的Java类型,即称为类加载机制。
2.类加载机制的五个阶段
加载——验证——准备——解析——初始化
这个过程 表示的是按顺序开始,而并不是第一步,第二步,第三步的关系,往往是交叉混合进行的,其中为了支持动态绑定,解析这个过程可以发生在初始化阶段之后进行;
下面来具体描述下各个过程所做的事情:
1)加载
加载过程主要完成三件事:
a.通过全限定名(包名和类名)获取定义此类的二进制字节流(.class文件);
b.将二进制字节流文件所代表的的静态存储结构转换成方法区中的动态存储结构;
c.在内存中生成一个代表此类的java.lang.class文件,作为方法区中这个类的各种数据的访问入口;
2)验证
在验证阶段主要确保被加载的类是否有正确的结构,数据是否符合虚拟机要求,主要包括四个阶段:
a.文件格式验证:基于字节流验证,验证字节流是否符合当前class文件格式的规范,能否被JVM处理,文件格式验证通过后,字节流才会进入内存的方法区进行处理;
b.元数据验证:基于方法区的存储结构验证,对字节码进行语义规范验证,确保不会存在不符合java语言规范的元数据信息;
c.字节码验证:基于方法区的存储结构验证,通过对数据流和控制流的分析,保证被验证的类的方法在运行时不会做出危害JVM的动作;
d.符号引用验证:基于方法区的存储结构验证,发生在解析阶段,确保能够将符号引用成功的解析为直接引用,确保解析动作正常执行
3)准备
为类的静态变量在方法区中分配内存,并设置默认初始值(0/null),例如 static int a =100,则a的默认赋值为0,一般的成员变量则在实例化时随对象一起分配在堆
内存中,静态常量则会赋程序设定的初始值,static final int a = 666,则a赋值为666;
4)解析
主要将常量池中的符合引用替换为直接引用的过程。
符号引用使用一组符号来描述目标,而直接引用则是直接指向目标的指针,相对偏移量或者是一个间接定位到目标的句柄,通常情况下一个符号引用在不同的JVM
实例中解析出来的直接引用是不同的。即通过这个过程我们可以找到目标的地址。java在编译过程中只会形成.class文件,并不会进行链接操作,所以编译阶段的java类
是不知道引用类的实际地址的。所以使用“符号引用”来代表引用类。
比如说,在com.auto.Person类中引用了com.auto.animal类,在编译过程中Person类并不知道animal类的实际地址,所以只能够用com.auto.animal来代表animal真
是的内存地址。在解析阶段,JVM可以通过该符号引用来确定animal类的真实内存地址。
5)初始化
在这个才真正执行java类中的代码,并且初始化变量和其他资源,为静态变量赋程序设定的初值;
java规范中规定了有且只有五种情况必须对类进行初始化
a.用new创建一个类的实例,或者用getstatic,putstatic读取或者设置一个静态字段的值;
b.通过java.lang.reflect包中的方法对类进行反射调用的时候,如果没有初始化则首先进行初始化;
c.当初始化一个类时,如果他的父类没有进行初始化,则需对他的父类先进行初始化;
d.当JVM启动时,需要指定一个主类,虚拟机会首先初始化这个类;
e.使用java1.7动态语言支持时,如果一个java.lang.invoke.methodhandle实例最后的解析结果ref_getstatic,ref_putstatic,re_invokestatic的方法句柄,并且这个方法句柄
对应的类没有进行初始化,首先触发其初始化;
3.