JAVA类是在运行时进行转载的,这种动态机制虽然降低了些许性能,但是使用起来更加灵活。相比编译时候需要连接的语言C++等来说。
类加载过程分为:加载-验证-准备-解析-初始化-使用-卸载。其中加载、验证、初始化的开始顺序是固定,但是解析有可能放在初始化后面。并且他们也有可能是交叉进行的。
初始化阶段
jvm对加载没有强制的规定,但是对类的初始化有了强制的规定。即对一个类的主动引用,主动因为指的是一下5中情况。
- new对象、调用对象的static属性(final除外因为final直接进入了运行时常量池)、调用对象的static方法。
- 初始化一个类时候发现父类没有初始化要对父类进行初始化
- main方法所以在的类
- 反射时候如果发现类没有初始化
当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。(实际上是因为第一条的规定)
注意:接口初始化和类很类似也需要初始化父类接口的class对象,不过不需要初始化所有父类接口,只有使用父类时候才会初始化(使用父类接口的常量)。
加载过程
加载
这里只的是类加载的“加载过程”。记载分为3个过程
- 根据类全限定名找到对应外部二进制文件
- 将二进制文件加载到内存中
在方法区创建对应的class类做外部访问数据的接口
几种外部的二级制文件加载方式zip包、网络、运行时、数据库等等
注意:数组的加载方式,由虚拟机直接产生。去掉数组的一维,如果是引用类型的话即组件类,该组件类的Classloader加载该类,并且数组类会在组件类的Classloader的的类空间进行标示。如果是int等非引用类型,我们会调用引导类的类加载器。访问方式取决于组件类的访问方式。
验证
java虚拟机校验类文件的合法性,防止恶意篡改、类错误导致虚拟机或者程序崩溃。主要有以下四个阶段
- 文件格式校验:判断class的魔数、版本等,通过校验后会将二进制流读入到方法区,后续的校验会针对方法区的二进制流进行校验(检查文件)
- 元数据校验:对字节码元数据进行分析,查看是否有父类,父类是否合法。(检查类)
- 字节码校验:保证方法体字节码的合法性,比如是否有非法的类型转换、赋值能。jdk1.7后增加了StackMapTable用来保存方法体中初始的状态,校验时候直接通过读取StackMapTable即可(检查方法)
- 符合引用校验:字段的引用是否合法,是否访问了不存在的字段、方法、以及超过了访问权限等
准备
java虚拟机为类的变量分配内存的过程,这里指的是static修饰的变量因为实例变量会在实例创建时候赋值。对于一般情况变量分配完内存后要赋0值,真实的值保存在<clinit>()方法中,在初始化时候才会赋值。final修饰的变量会在该阶段赋值真实值
解析
java将class文件中的符号引用改为直接引用的过程
字段的解析
- 普通引用比如对于字段N类型C来说,将N交给类的ClassLoader加载C的类型
- 数组加载数组的ComponentType的类,然后由虚拟机生成数组的直接应用
校验范围权限
类方法的解析
检查类的class_index如果是类方法,class_index是接口方法直接抛出java.lang.IncompatibleClassChangeError异常。
- 类中有方法的简单名称和描述符返回,否则去父类查找如果有返回,否则去接口查找如果接口有抛异常java.lang.AccessMethodError,如果没有抛出java.lang.NoSuchMethodError
检查访问权限
接口方法解析:步骤同上只是不用检查权限
初始化
在该阶段开始执行java代码(字节码),生成方法<clinit>()。
- clinit方法是由静态字段和静态代码块共同合并生成,且顺序自上到下
- 如果有父类,且父类有有静态类和静态字段,父类的clinit方法会先调用
- 接口因为也可能有静态阶段,接口也可能生成clinit,如果有父类接口,只有用到父类接口的变量才会生成父类接口的clinit方法
注意clinit出于线程安全考虑会加锁这就是为什么static代码块只执行一次。如果static里阻塞会阻塞类的创建
类加载器
唯一性:ClassLoader+类=唯一,每个ClassLoader都有自己的类命名空间,这个会影响到instanceOf等结果。
java的classloader分为3种
- 启动类加载器:Bootstrap ClassLoader,由C++编写,java无法直接调用,如果将类委托给该加载器调用改classloader回报异常。用于加载JAVA_HOME/lib以及 -Xbootclasspath对应地址的类并且要符合命名规则,比如rt.jar
- 扩展类加载器:Extension ClassLoader,由java编写,java可直接调用。JAVA_HOME/ext/lib或者java.ext.dirs指定的类
- 应用类加载器:Application ClassLoader,由java编写,java可直接调用。classpath中的类,如果不指定自己的classloader他是默认的classloader
类加载的双亲委派模型:即类加载器会优先调用父类的加载器,父类加载器会调用直到启动类加载器,之后在调用自己的类加载器。好处在于对于一些系统的类全局只有一个唯一的Class类(受唯一性约束)。顺序是:启动类加载器–>扩展类加载器–>应用类加载器–>自定义类加载器。
这是java建议的使用方法
双亲委派模型的代码
1 | protected synchronized Class<?>loadClass(String name,boolean resolve)throws ClassNotFoundException { |